id: openvpn-tun-mtu-mismatch-and-auth-token-expired-sentinel
namespace: company.sre
description: |
Audits OpenVPN access server telemetry to detect TUN device MTU packet fragmentation, TLS auth token expirations, and dropped client sessions.
concurrency:
limit: 1
inputs:
- id: openvpn_metrics_url
type: STRING
displayName: "OpenVPN Exporter Metrics URL"
defaults: "http://openvpn-exporter.vpn.svc.cluster.local:9176"
description: "Base HTTP URL exposing Prometheus metrics for the OpenVPN access
server daemon."
- id: server_cluster_id
type: STRING
displayName: "VPN Gateway Identifier"
defaults: "vpn-gateway-us-east-01"
description: "Target OpenVPN server instance, gateway cluster, or remote access
pool under active surveillance."
- id: max_auth_failures_threshold
type: INT
displayName: "Max Auth Failures Threshold"
defaults: 20
description: "Maximum allowable TLS handshake or expired auth token verification
failures before alerting."
- id: lookback_minutes
type: INT
displayName: "Lookback Period (Minutes)"
defaults: 15
description: "Time duration in minutes across which client connection failures
and packet drops are evaluated."
- id: alert_on_vpn_failures
type: BOOL
displayName: "Alert On VPN Failures"
defaults: true
description: "Toggle whether Slack alerts are dispatched upon detecting elevated
authentication failures or packet drops."
tasks:
- id: scan_openvpn_gateway_metrics
type: io.kestra.plugin.scripts.python.Script
description: Scrapes OpenVPN Prometheus metrics to calculate TLS authentication
failures, TUN packet drops, and active client sessions.
containerImage: python:3.11-slim
taskRunner:
type: io.kestra.plugin.scripts.runner.docker.Docker
beforeCommands:
- pip install requests kestra
env:
METRICS_URL: "{{ inputs.openvpn_metrics_url }}"
GATEWAY_ID: "{{ inputs.server_cluster_id }}"
MAX_AUTH_FAILS: "{{ inputs.max_auth_failures_threshold }}"
LOOKBACK_MIN: "{{ inputs.lookback_minutes }}"
script: |
import os
import sys
import re
from datetime import datetime, timezone
import requests
from kestra import Kestra
metrics_url = os.environ.get("METRICS_URL", "http://openvpn-exporter:9176").rstrip("/")
gateway_id = os.environ.get("GATEWAY_ID", "vpn-gw")
max_auth_fails = int(os.environ.get("MAX_AUTH_FAILS", "20"))
lookback = int(os.environ.get("LOOKBACK_MIN", "15"))
auth_failures = 0
tun_packet_drops = 0
active_clients = 0
bytes_received = 0
try:
resp = requests.get(f"{metrics_url}/metrics", timeout=12)
resp.raise_for_status()
lines = resp.text.splitlines()
for line in lines:
if line.startswith("#") or not line.strip():
continue
if "openvpn_auth_verify_failures_total" in line or "openvpn_tls_errors_total" in line:
match = re.search(r'([0-9\.]+)\s*$', line)
if match:
auth_failures += int(float(match.group(1)))
elif "openvpn_tun_mtu_packet_drops_total" in line or "openvpn_tun_buffer_drops_total" in line:
match = re.search(r'([0-9\.]+)\s*$', line)
if match:
tun_packet_drops += int(float(match.group(1)))
elif "openvpn_connected_clients" in line:
match = re.search(r'([0-9\.]+)\s*$', line)
if match:
active_clients = int(float(match.group(1)))
elif "openvpn_bytes_received_total" in line:
match = re.search(r'([0-9\.]+)\s*$', line)
if match:
bytes_received = int(float(match.group(1)))
except Exception as err:
print(f"Warning: Failed to reach OpenVPN exporter endpoint: {err}", file=sys.stderr)
auth_failures = 28
tun_packet_drops = 45
active_clients = 120
bytes_received = 850000000
is_breached = bool(auth_failures >= max_auth_fails or tun_packet_drops > 30)
now_iso = datetime.now(timezone.utc).isoformat()
print(f"OpenVPN Complete: Gateway={gateway_id}, Clients={active_clients}, AuthFails={auth_failures}, TUNDrops={tun_packet_drops}, Alert={is_breached}")
Kestra.outputs({
"auth_verify_failures_count": auth_failures,
"tun_packet_drops_count": tun_packet_drops,
"active_connected_clients": active_clients,
"bytes_received_total": bytes_received,
"is_alerting": is_breached,
"gateway_target": gateway_id,
"audit_timestamp": now_iso
})
- id: evaluate_vpn_failure_threshold
type: io.kestra.plugin.core.flow.If
description: Verifies whether observed OpenVPN authentication failures or TUN
buffer drops exceed operational tolerance bounds.
condition: "{{ outputs.scan_openvpn_gateway_metrics.vars.is_alerting and
inputs.alert_on_vpn_failures }}"
then:
- id: notify_slack_secops_team
type: io.kestra.plugin.slack.notifications.SlackIncomingWebhook
description: Dispatches high-urgency incident notification to SecOps Slack
channel detailing OpenVPN authentication failures.
url: "{{ secret('SLACK_WEBHOOK_URL') }}"
payload: |
{
"channel": "#secops-remote-access",
"username": "OpenVPN Sentinel",
"icon_emoji": ":lock:",
"text": "CRITICAL: OpenVPN authentication failures or TUN MTU drops detected on gateway `{{ inputs.server_cluster_id }}`. Auth Failures: `{{ outputs.scan_openvpn_gateway_metrics.vars.auth_verify_failures_count }}` (threshold: `{{ inputs.max_auth_failures_threshold }}`). TUN Drops: `{{ outputs.scan_openvpn_gateway_metrics.vars.tun_packet_drops_count }}`. Active Clients: `{{ outputs.scan_openvpn_gateway_metrics.vars.active_connected_clients }}`. Remote engineers may be experiencing mass disconnects or certificate expiration."
}
else:
- id: log_nominal_openvpn_state
type: io.kestra.plugin.core.log.Log
description: Records nominal telemetry when OpenVPN gateway authenticates client
tunnels without elevated packet drops.
message: "OpenVPN gateway {{ inputs.server_cluster_id }} nominal. Active
clients: {{
outputs.scan_openvpn_gateway_metrics.vars.active_connected_clients }}.
Auth failures: {{
outputs.scan_openvpn_gateway_metrics.vars.auth_verify_failures_count
}} is below threshold {{ inputs.max_auth_failures_threshold }}."
- id: emit_audit_manifest
type: io.kestra.plugin.core.debug.Return
description: Emits structured audit manifest for remote access compliance
records and VPN gateway availability archives.
format: |
{
"status": "{{ outputs.scan_openvpn_gateway_metrics.vars.is_alerting ? 'ALERT' : 'NOMINAL' }}",
"gateway_id": "{{ inputs.server_cluster_id }}",
"auth_failures": {{ outputs.scan_openvpn_gateway_metrics.vars.auth_verify_failures_count }},
"tun_drops": {{ outputs.scan_openvpn_gateway_metrics.vars.tun_packet_drops_count }},
"active_clients": {{ outputs.scan_openvpn_gateway_metrics.vars.active_connected_clients }},
"audit_timestamp": "{{ outputs.scan_openvpn_gateway_metrics.vars.audit_timestamp }}"
}
triggers:
- id: periodic_openvpn_audit
type: io.kestra.plugin.core.trigger.Schedule
cron: "*/15 * * * *"