EAS Station Security Guide

Overview

EAS Station implements comprehensive security controls including Role-Based Access Control (RBAC), Multi-Factor Authentication (MFA), and detailed audit logging. This guide covers setup, configuration, and best practices for securing your EAS Station deployment.

Table of Contents

  1. Quick Start
  2. Role-Based Access Control
  3. Multi-Factor Authentication
  4. Audit Logging
  5. API Endpoints
  6. Security Best Practices
  7. Troubleshooting

Quick Start

1. Apply Database Migration

After pulling the latest changes, apply the security migration:

2. Initialize Default Roles

Initialize the three default roles (admin, operator, viewer) and their permissions:

curl -X POST http://localhost:5000/security/init-roles \
  -H "Content-Type: application/json" \
  -b cookies.txt

Or use the API endpoint after logging in as an admin.

3. Assign Roles to Users

Assign roles to existing users:

from app_core.models import AdminUser
from app_core.auth.roles import Role
from app_core.extensions import db

# Get user and role
user = AdminUser.query.filter_by(username='your_username').first()
admin_role = Role.query.filter_by(name='admin').first()

# Assign role
user.role_id = admin_role.id
db.session.commit()

Or via API:

curl -X PUT http://localhost:5000/security/users/1/role \
  -H "Content-Type: application/json" \
  -d '{"role_id": 1}' \
  -b cookies.txt

Role-Based Access Control

Default Roles

EAS Station provides three predefined roles:

1. Admin (Full Access)

  • All system permissions
  • User and role management
  • System configuration
  • Alert and EAS operations
  • Log management
  • GPIO and hardware control

2. Operator (Operations Access)

  • Alert viewing and creation
  • EAS broadcast and manual activation
  • GPIO control
  • Log viewing and export
  • Cannot: Manage users, modify system configuration, delete data

3. Viewer (Read-Only)

  • View alerts, logs, and system status
  • Export logs
  • Cannot: Create/delete data, broadcast EAS, control GPIO, manage users

Permission Model

Permissions follow the format: resource.action

Resources:

  • alerts - CAP alerts and EAS messages
  • eas - EAS broadcast operations
  • system - System configuration and user management
  • logs - System and audit logs
  • receivers - SDR receivers
  • gpio - GPIO relay control
  • api - API access

Actions:

  • view - Read access
  • create - Create new records
  • delete - Delete records
  • export - Export data
  • configure - Modify configuration
  • control - Activate/control hardware
  • manage_users - User administration

Using Permission Decorators

In your route handlers:

from app_core.auth.roles import require_permission

@app.route('/admin/users')
@require_permission('system.manage_users')
def manage_users():
    # Only accessible by users with system.manage_users permission
    pass

@app.route('/alerts')
@require_permission('alerts.view')
def view_alerts():
    # Accessible by users with alerts.view permission
    pass

Multiple permission patterns:

from app_core.auth.roles import require_any_permission, require_all_permissions

# Require ANY of these permissions
@require_any_permission('alerts.view', 'alerts.create')
def alerts_page():
    pass

# Require ALL of these permissions
@require_all_permissions('alerts.delete', 'system.configure')
def critical_operation():
    pass

Checking Permissions Programmatically

from app_core.auth.roles import has_permission

if has_permission('eas.broadcast'):
    # User can broadcast
    pass

Creating Custom Roles

Via Python:

from app_core.auth.roles import Role, Permission
from app_core.extensions import db

# Create a custom role
custom_role = Role(
    name='technician',
    description='Technical support staff with limited access'
)

# Assign specific permissions
perms = Permission.query.filter(Permission.name.in_([
    'receivers.view',
    'receivers.configure',
    'logs.view',
    'system.view_config'
])).all()

custom_role.permissions.extend(perms)
db.session.add(custom_role)
db.session.commit()

Via API:

curl -X POST http://localhost:5000/security/roles \
  -H "Content-Type: application/json" \
  -d '{
    "name": "technician",
    "description": "Technical support role",
    "permission_ids": [1, 2, 5, 8]
  }' \
  -b cookies.txt

Multi-Factor Authentication

Overview

EAS Station supports TOTP-based two-factor authentication compatible with:

  • Google Authenticator
  • Microsoft Authenticator
  • Authy
  • Any RFC 6238 compliant authenticator app

Enrolling in MFA

1. Start Enrollment

curl -X POST http://localhost:5000/security/mfa/enroll/start \
  -b cookies.txt

Response includes:

{
  "secret": "JBSWY3DPEHPK3PXP",
  "provisioning_uri": "otpauth://totp/EAS%20Station:username?secret=...",
  "message": "Scan the QR code with your authenticator app..."
}

2. Get QR Code

Open in browser while logged in:

http://localhost:5000/security/mfa/enroll/qr

Or via curl:

curl http://localhost:5000/security/mfa/enroll/qr \
  -b cookies.txt \
  -o mfa_qr.png

3. Verify Setup

Enter the 6-digit code from your authenticator app:

curl -X POST http://localhost:5000/security/mfa/enroll/verify \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}' \
  -b cookies.txt

Response includes:

{
  "success": true,
  "backup_codes": [
    "A1B2C3D4",
    "E5F6G7H8",
    ...
  ],
  "message": "MFA enrolled successfully..."
}

IMPORTANT: Save your backup codes in a secure location!

Login Flow with MFA

  1. Enter username and password
  2. If MFA is enabled, redirected to /mfa/verify
  3. Enter 6-digit TOTP code from authenticator app
  4. Alternatively, use an 8-character backup code
  5. Session established after successful verification

MFA Session Timeout

  • Partial authentication (post-password) expires in 5 minutes
  • Must complete MFA verification within timeout window
  • Expired sessions require starting login process again

Backup Codes

  • 10 backup codes generated during enrollment
  • Each code is single-use
  • 8 characters, alphanumeric (e.g., "A1B2C3D4")
  • Hashed in database (bcrypt)
  • Used when authenticator app unavailable

Disabling MFA

Requires password confirmation:

curl -X POST http://localhost:5000/security/mfa/disable \
  -H "Content-Type: application/json" \
  -d '{"password": "your_password"}' \
  -b cookies.txt

Checking MFA Status

curl http://localhost:5000/security/mfa/status \
  -b cookies.txt

Audit Logging

Overview

All security-sensitive operations are logged to the audit_logs table with:

  • Timestamp (UTC)
  • User ID and username
  • Action type (e.g., auth.login.success, mfa.enrolled)
  • Resource affected (type and ID)
  • IP address and user agent
  • Success/failure status
  • Additional details (JSON)

Audit Actions Tracked

Authentication:

  • auth.login.success / auth.login.failure
  • auth.logout
  • auth.session.expired

MFA:

  • mfa.enrolled / mfa.disabled
  • mfa.verify.success / mfa.verify.failure
  • mfa.backup_code.used

User Management:

  • user.created / user.updated / user.deleted
  • user.activated / user.deactivated
  • user.role.changed
  • user.password.changed

Permissions:

  • role.created / role.updated / role.deleted
  • permission.granted / permission.revoked

Operations:

  • eas.broadcast / eas.manual_activation / eas.cancellation
  • config.updated
  • gpio.activated / gpio.deactivated
  • alert.deleted / log.exported / log.deleted

Security Events:

  • security.permission_denied
  • security.invalid_token
  • security.rate_limit_exceeded

Viewing Audit Logs

Via API

# Recent logs (last 30 days)
curl 'http://localhost:5000/security/audit-logs?days=30' \
  -b cookies.txt

# Filter by user
curl 'http://localhost:5000/security/audit-logs?user_id=1' \
  -b cookies.txt

# Filter by action
curl 'http://localhost:5000/security/audit-logs?action=auth.login.failure' \
  -b cookies.txt

# Only failed operations
curl 'http://localhost:5000/security/audit-logs?success=false' \
  -b cookies.txt

Export as CSV

curl 'http://localhost:5000/security/audit-logs/export?days=90' \
  -b cookies.txt \
  -o audit_logs.csv

Programmatic Logging

from app_core.auth.audit import AuditLogger, AuditAction

# Simple logging
AuditLogger.log(
    action=AuditAction.CONFIG_UPDATED,
    resource_type='audio_source',
    resource_id='1',
    details={'field': 'volume', 'old_value': 0.8, 'new_value': 0.9}
)

# Convenience methods
AuditLogger.log_login_success(user_id, username)
AuditLogger.log_permission_denied(user_id, username, 'system.configure', '/admin/settings')

Retention Management

Clean up old audit logs:

from app_core.auth.audit import AuditLogger

# Delete logs older than 90 days
deleted_count = AuditLogger.cleanup_old_logs(days=90)

Recommended retention: 90 days (FCC compliance may require longer)


API Endpoints

Authentication

  • POST /login - Username/password authentication
  • POST /mfa/verify - MFA verification
  • GET /logout - Sign out

MFA Management

  • GET /security/mfa/status - Check MFA status
  • POST /security/mfa/enroll/start - Start MFA enrollment
  • GET /security/mfa/enroll/qr - Get QR code image
  • POST /security/mfa/enroll/verify - Complete enrollment
  • POST /security/mfa/disable - Disable MFA

Role Management

  • GET /security/roles - List all roles
  • GET /security/roles/<id> - Get role details
  • POST /security/roles - Create custom role
  • PUT /security/roles/<id> - Update role
  • GET /security/permissions - List all permissions
  • PUT /security/users/<id>/role - Assign role to user

Audit Logs

  • GET /security/audit-logs - List audit logs (paginated, filterable)
  • GET /security/audit-logs/export - Export logs as CSV

Utilities

  • POST /security/init-roles - Initialize default roles
  • POST /security/permissions/check - Check if user has permission

Security Best Practices

1. Enable MFA for All Admin Users

Require MFA for users with admin role:

# Enforce MFA policy
admin_users = AdminUser.query.join(Role).filter(Role.name == 'admin').all()
for user in admin_users:
    if not user.mfa_enabled:
        # Send notification to enable MFA
        pass

2. Principle of Least Privilege

  • Assign viewer role by default
  • Grant operator role only when needed
  • Limit admin role to system administrators

3. Regular Audit Log Review

Schedule weekly reviews:

# Failed login attempts
curl 'http://localhost:5000/security/audit-logs?action=auth.login.failure&days=7' \
  -b cookies.txt

# Permission denied events
curl 'http://localhost:5000/security/audit-logs?action=security.permission_denied&days=7' \
  -b cookies.txt

4. Session Management

Configure secure session settings in app.py:

app.config['SESSION_COOKIE_SECURE'] = True       # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True     # Prevent JavaScript access
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'   # CSRF protection
app.config['PERMANENT_SESSION_LIFETIME'] = 3600  # 1 hour timeout

5. Network Security

  • Run EAS Station behind reverse proxy (nginx/Apache)
  • Enable HTTPS with valid TLS certificates
  • Use firewall rules to restrict admin access
  • Consider VPN for remote administration

6. Password Policies

Implement strong password requirements:

import re

def validate_password(password):
    """Enforce password complexity."""
    if len(password) < 12:
        return False, "Password must be at least 12 characters"
    if not re.search(r'[A-Z]', password):
        return False, "Password must contain uppercase letter"
    if not re.search(r'[a-z]', password):
        return False, "Password must contain lowercase letter"
    if not re.search(r'[0-9]', password):
        return False, "Password must contain number"
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        return False, "Password must contain special character"
    return True, "Valid"

7. Backup Security Data

Regularly backup:

  • User accounts and roles
  • Audit logs (before cleanup)
  • Security configuration
pg_dump -U postgres -d eas_station -t admin_users -t roles -t permissions -t audit_logs > security_backup.sql

8. Monitor for Anomalies

Set up alerts for:

  • Multiple failed login attempts
  • MFA verification failures
  • Permission denied events
  • Unexpected role changes
  • Admin account creation

Troubleshooting

User Cannot Log In After Migration

Symptom: User has no role assigned, all routes return 403 Forbidden

Solution: Assign a role to the user

from app_core.models import AdminUser
from app_core.auth.roles import Role
from app_core.extensions import db

user = AdminUser.query.filter_by(username='username').first()
admin_role = Role.query.filter_by(name='admin').first()
user.role_id = admin_role.id
db.session.commit()

MFA Enrollment Fails

Symptom: "pyotp is required" error

Solution: Ensure dependencies are installed

pip install pyotp==2.9.0 qrcode==8.0

QR Code Not Displaying

Symptom: 500 error when accessing /security/mfa/enroll/qr

Solution: Check that Pillow is installed

pip install Pillow==10.4.0

Permission Decorator Not Working

Symptom: Routes accessible even without permission

Solution: Ensure user has a role assigned and role has the required permission

from app_core.auth.roles import has_permission

# Debug permission check
user_id = session.get('user_id')
user = AdminUser.query.get(user_id)
print(f"User role: {user.role.name if user.role else None}")
print(f"Has permission: {has_permission('alerts.view')}")

Audit Logs Growing Too Large

Symptom: Database size increasing rapidly

Solution: Implement scheduled cleanup

# Add to scheduled task (cron/celery)
from app_core.auth.audit import AuditLogger

# Keep 90 days of logs
AuditLogger.cleanup_old_logs(days=90)

Session Expires During MFA

Symptom: "Session expired. Please log in again." after entering password

Solution: Complete MFA verification within 5 minutes. If consistently timing out, increase timeout:

# In app_core/auth/mfa.py
class MFASession:
    TIMEOUT_MINUTES = 10  # Increase from 5 to 10

Migration Checklist

When deploying security features to existing EAS Station:

  • Pull latest code from security branch
  • Install new dependencies: pip install -r requirements.txt
  • Run database migration: flask db upgrade
  • Initialize default roles: POST /security/init-roles
  • Assign roles to all existing users
  • Test login with each role
  • Enroll MFA for admin users
  • Test MFA login flow
  • Review audit log functionality
  • Update documentation/procedures
  • Configure log retention policy
  • Set up audit log monitoring
  • Enable HTTPS if not already active
  • Review session timeout settings

Additional Resources


Support

For security issues or questions:

Security Vulnerabilities: Report privately via email, not public issues.


Security Features

This section describes the comprehensive security features implemented to protect against malicious login attempts, SQL injection, command injection, and brute force attacks.

1. Input Validation

Location: app_core/auth/input_validation.py

The input validator checks for:

  • SQL injection patterns (' OR 1=1, UNION SELECT, etc.)
  • Command injection patterns (&&, ||, ;, backticks, etc.)
  • Non-printable characters
  • Overly long inputs

All malicious inputs are:

  • Rejected immediately
  • Sanitized before logging
  • Logged for security analysis
  • Trigger automatic IP banning (24 hours)

2. Rate Limiting

Location: app_core/auth/rate_limiter.py

Configuration:

  • Max attempts: 5 failed logins
  • Lockout duration: 15 minutes
  • Attempt window: 5 minutes

Features:

  • Per-IP tracking of failed attempts
  • Automatic lockout after threshold
  • Cleanup of old entries

3. Flood Protection

Location: app_core/auth/ip_filter.py (FloodProtection class)

Configuration:

  • Max attempts per minute: 10
  • Auto-ban duration: 1 hour

Detects and automatically bans IPs making rapid-fire login attempts.

4. IP Filtering (Allowlist/Blocklist)

Location: app_core/auth/ip_filter.py

Database Table: ip_filters

Features:

  • Allowlist: Trusted IPs/ranges that bypass all checks
  • Blocklist: Banned IPs/ranges that cannot access the system
  • CIDR range support (e.g., 192.168.1.0/24)
  • Optional expiration times
  • Active/inactive toggle
  • Manual and automatic entries

Auto-ban Triggers:

  1. Malicious Input (24 hours): SQL/command injection attempt
  2. Brute Force (24 hours): 5 failed login attempts
  3. Flooding (1 hour): >10 attempts per minute

5. Security Logging

Location: app_core/auth/security_logger.py

Log File: /var/log/eas-station/security.log

Event Types:

  • MALICIOUS_LOGIN: SQL/command injection attempt
  • FAILED_LOGIN: Regular failed login
  • RATE_LIMIT_EXCEEDED: Too many attempts

Format (fail2ban compatible):

[YYYY-MM-DD HH:MM:SS UTC] EVENT_TYPE from IP_ADDRESS username=USERNAME details=DETAILS

6. Audit Logging

Location: app_core/auth/audit.py

Database Table: audit_logs

Comprehensive audit trail of all security events including:

  • Login successes/failures
  • MFA events
  • IP filter changes
  • Permission denials

fail2ban Integration

Installation

  1. Install fail2ban:
sudo apt-get install fail2ban
  1. Configure Jail (/etc/fail2ban/jail.local):
[eas-station-malicious]
enabled = true
port = http,https
filter = eas-station-malicious
logpath = /var/log/eas-station/security.log
maxretry = 1
bantime = 3600
findtime = 600

[eas-station-auth]
enabled = true
port = http,https
filter = eas-station-auth
logpath = /var/log/eas-station/security.log
maxretry = 5
bantime = 1800
findtime = 600
  1. Create Malicious Filter (/etc/fail2ban/filter.d/eas-station-malicious.conf):
[Definition]
failregex = ^.*MALICIOUS_LOGIN from <HOST>.*$
ignoreregex =
  1. Create Auth Filter (/etc/fail2ban/filter.d/eas-station-auth.conf):
[Definition]
failregex = ^.*(FAILED_LOGIN|RATE_LIMIT_EXCEEDED) from <HOST>.*$
ignoreregex =
  1. Restart fail2ban:
sudo systemctl restart fail2ban

Testing fail2ban

# Check status
sudo fail2ban-client status eas-station-malicious
sudo fail2ban-client status eas-station-auth

# View banned IPs
sudo fail2ban-client get eas-station-malicious banned

# Unban an IP
sudo fail2ban-client set eas-station-malicious unbanip 192.168.1.1

IP Filter Web Interface

Malicious Login Attempts Page

URL: /admin/malicious-logins

Features:

  • View all malicious login attempts
  • Statistics dashboard (total attempts, unique IPs, today's attempts)
  • Top attacking IP addresses
  • Quick ban from statistics
  • fail2ban configuration viewer

IP Filter Management

Location: Same page as malicious logins

Actions:

  • Add to allowlist/blocklist
  • Support for single IPs or CIDR ranges
  • Set expiration time (blocklist only)
  • Toggle active/inactive
  • Delete filters
  • View reason and description

Example Allowlist Entries:

  • 192.168.1.0/24 - Internal network
  • 10.0.0.5 - Admin workstation

Example Blocklist Entries:

  • 1.2.3.4 - Known attacker
  • 5.6.7.0/24 - Malicious subnet

IP Filter API Endpoints

  • GET /security/ip-filters - List all filters
  • POST /security/ip-filters - Add new filter
  • DELETE /security/ip-filters/<id> - Delete filter
  • POST /security/ip-filters/<id>/toggle - Toggle active status
  • POST /security/ip-filters/cleanup - Clean up expired filters
  • GET /security/malicious-login-attempts - List malicious attempts with IP statistics

Security Feature Configuration

Rate Limiting

Edit app_core/auth/rate_limiter.py:

MAX_ATTEMPTS = 5  # Maximum failed attempts before lockout
LOCKOUT_DURATION = timedelta(minutes=15)  # How long to lock out
ATTEMPT_WINDOW = timedelta(minutes=5)  # Time window to count attempts

Flood Protection

Edit app_core/auth/ip_filter.py:

MAX_ATTEMPTS_PER_MINUTE = 10  # Max attempts per minute
FLOOD_BAN_HOURS = 1  # Ban duration for flooding

Auto-Ban

Edit app_core/auth/ip_filter.py:

FAILED_ATTEMPTS_THRESHOLD = 5  # Failed attempts before auto-ban
BAN_DURATION_HOURS = 24  # Ban duration

Security Monitoring

Recommended monitoring:

  • Track failed login attempts per hour
  • Alert on >50 malicious attempts in 1 hour
  • Alert on auto-ban triggers
  • Weekly review of blocklist entries
  • Monitor fail2ban ban count

Locked Out Admin

If an admin IP is accidentally blocked:

  1. Via Database:
DELETE FROM ip_filters WHERE ip_address = '1.2.3.4';
  1. Via fail2ban:
sudo fail2ban-client set eas-station-auth unbanip 1.2.3.4

Check Security Logs

tail -f /var/log/eas-station/security.log

This document is served from docs/security/SECURITY.md in the EAS Station installation.