Merge pull request #208 from outis1one/claude/sip-voip-integration-atsins
Claude/sip voip integration atsins
This commit is contained in:
@@ -270,6 +270,29 @@ _asterisk_do_refresh_vendor_files() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Shared: log rotation for logs/full (unbounded otherwise) ──────────────
|
||||||
|
# Confirmed live: with no rotation, this file grew to 1.4GB in about 3 days
|
||||||
|
# on a busy box (SIP scanning noise is constant on the public internet) —
|
||||||
|
# a real disk-exhaustion risk on a small droplet, and separately made the
|
||||||
|
# Security Dashboard balloon to 600+MB RAM/GBs of swap reading it every 30s
|
||||||
|
# before that was fixed to only read a bounded tail (see
|
||||||
|
# services/security-dashboard.sh). copytruncate avoids needing to signal
|
||||||
|
# Asterisk to reopen its log file — it has a long-held file descriptor on
|
||||||
|
# this path and no reload mechanism this installer can reach from the host.
|
||||||
|
_asterisk_do_write_logrotate() {
|
||||||
|
local _ea_dir="$1"
|
||||||
|
cat > /etc/logrotate.d/asterisk-digital-ocean << LOGROTATE
|
||||||
|
$_ea_dir/logs/full {
|
||||||
|
size 100M
|
||||||
|
rotate 5
|
||||||
|
compress
|
||||||
|
missingok
|
||||||
|
notifempty
|
||||||
|
copytruncate
|
||||||
|
}
|
||||||
|
LOGROTATE
|
||||||
|
}
|
||||||
|
|
||||||
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
# ── Shared: docker-compose.yml ─────────────────────────────────────────────
|
||||||
# Same reasoning as above — one copy of the template used by both fresh
|
# Same reasoning as above — one copy of the template used by both fresh
|
||||||
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
# installs and updates. Must be called with $PWD already at $EA_DIR.
|
||||||
@@ -379,6 +402,7 @@ install_asterisk-digital-ocean() {
|
|||||||
|
|
||||||
_asterisk_do_refresh_vendor_files
|
_asterisk_do_refresh_vendor_files
|
||||||
_asterisk_do_write_compose
|
_asterisk_do_write_compose
|
||||||
|
_asterisk_do_write_logrotate "$EA_DIR"
|
||||||
|
|
||||||
log_info "Rebuilding and restarting containers..."
|
log_info "Rebuilding and restarting containers..."
|
||||||
if docker compose up -d --build --force-recreate; then
|
if docker compose up -d --build --force-recreate; then
|
||||||
@@ -447,6 +471,7 @@ install_asterisk-digital-ocean() {
|
|||||||
cd "$EA_DIR" || return 1
|
cd "$EA_DIR" || return 1
|
||||||
|
|
||||||
_asterisk_do_refresh_vendor_files
|
_asterisk_do_refresh_vendor_files
|
||||||
|
_asterisk_do_write_logrotate "$EA_DIR"
|
||||||
|
|
||||||
# ── DigitalOcean droplet detection ────────────────────────────────────────
|
# ── DigitalOcean droplet detection ────────────────────────────────────────
|
||||||
# A droplet's own public IP/ID are readable, unauthenticated, from the
|
# A droplet's own public IP/ID are readable, unauthenticated, from the
|
||||||
|
|||||||
+18
-2
@@ -263,8 +263,24 @@ ASTENUM
|
|||||||
|
|
||||||
# Disable the hub originals so they don't double-process the
|
# Disable the hub originals so they don't double-process the
|
||||||
# same events alongside the ASN-exempt forks written above.
|
# same events alongside the ASN-exempt forks written above.
|
||||||
sudo cscli scenarios remove crowdsecurity/asterisk_bf crowdsecurity/asterisk_user_enum 2>/dev/null || true
|
# --force is required: these scenarios came in as part of the
|
||||||
echo " ✓ Wrote ASN-exempt local forks; disabled the hub originals"
|
# crowdsecurity/asterisk collection, and cscli refuses to
|
||||||
|
# remove/disable a collection member without it. Confirmed
|
||||||
|
# live: without --force this failed silently (stderr
|
||||||
|
# suppressed, "|| true" swallowed the non-zero exit), leaving
|
||||||
|
# the un-exempted hub original running side-by-side with the
|
||||||
|
# ASN-exempt fork the entire time — the fork's exemption
|
||||||
|
# never actually took effect for anyone, since the original
|
||||||
|
# scenario kept independently banning the same traffic with
|
||||||
|
# no ASN awareness at all.
|
||||||
|
if sudo cscli scenarios remove crowdsecurity/asterisk_bf crowdsecurity/asterisk_user_enum --force 2>/dev/null; then
|
||||||
|
echo " ✓ Wrote ASN-exempt local forks; disabled the hub originals"
|
||||||
|
else
|
||||||
|
log_warning "Failed to disable the hub-original asterisk_bf/asterisk_user_enum scenarios —"
|
||||||
|
log_warning "the ASN exemption below will NOT take effect until this is resolved. Run:"
|
||||||
|
log_warning " sudo cscli scenarios remove crowdsecurity/asterisk_bf crowdsecurity/asterisk_user_enum --force"
|
||||||
|
log_warning " sudo systemctl restart crowdsec"
|
||||||
|
fi
|
||||||
echo " ℹ Exempted ASNs: $ASN_LIST — SSH/web/geo-allowlist scenarios are unaffected"
|
echo " ℹ Exempted ASNs: $ASN_LIST — SSH/web/geo-allowlist scenarios are unaffected"
|
||||||
echo " ℹ Edit /etc/crowdsec/scenarios/local-asterisk_*.yaml to add/remove ASNs later"
|
echo " ℹ Edit /etc/crowdsec/scenarios/local-asterisk_*.yaml to add/remove ASNs later"
|
||||||
echo " (then: sudo systemctl restart crowdsec)"
|
echo " (then: sudo systemctl restart crowdsec)"
|
||||||
|
|||||||
@@ -588,6 +588,19 @@ if [[ "$found" != "1" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Self-healing: the hub-original crowdsecurity/asterisk_bf /
|
||||||
|
# asterisk_user_enum scenarios have no ASN awareness at all, so if they're
|
||||||
|
# still enabled alongside the exempt forks above, they independently ban
|
||||||
|
# the same traffic regardless of anything just written — the exemption
|
||||||
|
# above would silently do nothing. crowdsec.sh's original install is
|
||||||
|
# supposed to disable them (--force, since they're crowdsecurity/asterisk
|
||||||
|
# collection members), but an install from before that fix shipped (or one
|
||||||
|
# where that step failed silently) would still have them active. Re-assert
|
||||||
|
# it on every save rather than trusting it was ever done correctly once —
|
||||||
|
# confirmed live: an install where this step had silently failed kept
|
||||||
|
# banning an exempted ASN under the hub-original scenario name.
|
||||||
|
cscli scenarios remove crowdsecurity/asterisk_bf crowdsecurity/asterisk_user_enum --force 2>/dev/null || true
|
||||||
|
|
||||||
if ! systemctl restart crowdsec; then
|
if ! systemctl restart crowdsec; then
|
||||||
echo "Wrote ASN list but failed to restart CrowdSec" >&2
|
echo "Wrote ASN list but failed to restart CrowdSec" >&2
|
||||||
exit 2
|
exit 2
|
||||||
@@ -646,18 +659,43 @@ TIER_RE = re.compile(r"^(internal|restricted|full)$")
|
|||||||
NUMBER_RE = re.compile(r"^\d{11}$")
|
NUMBER_RE = re.compile(r"^\d{11}$")
|
||||||
|
|
||||||
|
|
||||||
|
SECURITY_LOG_TAIL_BYTES = 2 * 1024 * 1024 # comfortably enough for 5000 lines
|
||||||
|
|
||||||
|
|
||||||
def parse_security_log(limit=200):
|
def parse_security_log(limit=200):
|
||||||
"""Tail ASTERISK_LOG and return the most recent SecurityEvent lines,
|
"""Tail ASTERISK_LOG and return the most recent SecurityEvent lines,
|
||||||
newest first, as dicts. Missing file / no lines -> empty list, never an
|
newest first, as dicts. Missing file / no lines -> empty list, never an
|
||||||
error — this is a convenience view, not load-bearing."""
|
error — this is a convenience view, not load-bearing.
|
||||||
|
|
||||||
|
Reads only a bounded byte window from the END of the file, not the whole
|
||||||
|
thing — this log is Asterisk's unrotated console/security output and can
|
||||||
|
grow to multiple GB. The previous version did f.readlines() (loads the
|
||||||
|
ENTIRE file into memory) before slicing the last 5000 lines, and this
|
||||||
|
tab polls every 30 seconds from the browser. Confirmed live: on a 1GB-RAM
|
||||||
|
droplet with a 1.4GB log file, that ballooned this "stdlib only,
|
||||||
|
deliberately lightweight" process to 677MB RSS / 1.8GB peak swap, which
|
||||||
|
left CrowdSec unable to even start (boot timeout) and contributed
|
||||||
|
directly to the droplet becoming unresponsive. Bounding this to a fixed
|
||||||
|
~2MB window keeps memory use constant regardless of how large the log
|
||||||
|
file grows.
|
||||||
|
"""
|
||||||
if not ASTERISK_LOG or not os.path.isfile(ASTERISK_LOG):
|
if not ASTERISK_LOG or not os.path.isfile(ASTERISK_LOG):
|
||||||
return []
|
return []
|
||||||
events = []
|
events = []
|
||||||
try:
|
try:
|
||||||
with open(ASTERISK_LOG, "r", errors="replace") as f:
|
with open(ASTERISK_LOG, "rb") as f:
|
||||||
lines = f.readlines()[-5000:] # cap how much we ever scan
|
f.seek(0, os.SEEK_END)
|
||||||
|
size = f.tell()
|
||||||
|
start = max(0, size - SECURITY_LOG_TAIL_BYTES)
|
||||||
|
f.seek(start)
|
||||||
|
data = f.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
return []
|
return []
|
||||||
|
text = data.decode("utf-8", errors="replace")
|
||||||
|
lines = text.splitlines()
|
||||||
|
if start > 0 and lines:
|
||||||
|
lines = lines[1:] # first line is likely truncated mid-line
|
||||||
|
lines = lines[-5000:]
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if "SecurityEvent=" not in line:
|
if "SecurityEvent=" not in line:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user