security operations

Implementing Syslog Centralization with Rsyslog

Configure rsyslog for centralized log collection with TLS encryption, custom templates, and log rotation. Generates server and client configuration files with GnuTLS stream drivers, x509 certificate authentication, per-host log segregation, and reliable queue settings for high-availability syslog infrastructure.

log-centralizationlog-managementrsyslogsecurity-operationssyslogtls-encryption
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When deploying or configuring implementing syslog centralization with rsyslog capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Familiarity with security operations concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Instructions

  1. Install dependencies: pip install jinja2 paramiko
  2. Generate TLS certificates for rsyslog server and clients using OpenSSL.
  3. Run the agent to generate rsyslog server and client configurations:
    • Server: TLS listener on port 6514, per-host directory output, JSON-format templates
    • Client: TLS forwarding with disk-assisted queues for reliability
  4. Deploy configurations to servers via SSH (paramiko).
  5. Validate TLS connectivity and log delivery.
python scripts/agent.py --server-ip 10.0.0.1 --clients 10.0.0.10,10.0.0.11 --ca-cert ca.pem --output syslog_report.json

Examples

Server Configuration (TLS)

module(load="imtcp" StreamDriver.Name="gtls" StreamDriver.Mode="1"
       StreamDriver.Authmode="x509/name")
input(type="imtcp" port="6514")
template(name="PerHostLog" type="string" string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")
*.* ?PerHostLog

Client Configuration (Reliable Forwarding)

action(type="omfwd" target="10.0.0.1" port="6514" protocol="tcp"
       StreamDriver="gtls" StreamDriverMode="1"
       StreamDriverAuthMode="x509/name"
       queue.type="LinkedList" queue.filename="fwdRule1"
       queue.maxdiskspace="1g" queue.saveonshutdown="on"
       action.resumeRetryCount="-1")
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.9 KB

API Reference: Rsyslog Centralization with TLS

Rsyslog Server Configuration Directives

TLS Module Loading

module(load="imtcp"
    StreamDriver.Name="gtls"
    StreamDriver.Mode="1"
    StreamDriver.Authmode="x509/name"
    PermittedPeer=["client1.local","client2.local"])

Global TLS Settings

global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/path/to/ca.pem"
    DefaultNetstreamDriverCertFile="/path/to/cert.pem"
    DefaultNetstreamDriverKeyFile="/path/to/key.pem")

Template Syntax

template(name="PerHostDir" type="string"
    string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")
template(name="JSONFormat" type="string"
    string='{"host":"%HOSTNAME%","msg":"%msg:::json%"}\n')

Rsyslog Client Forwarding

action(type="omfwd" target="<server>" port="6514" protocol="tcp"
    StreamDriver="gtls" StreamDriverMode="1"
    StreamDriverAuthMode="x509/name"
    queue.type="LinkedList" queue.filename="fwdRule1"
    queue.maxdiskspace="1g" queue.saveonshutdown="on"
    action.resumeRetryCount="-1")

Jinja2 Template Engine

from jinja2 import Template
tmpl = Template("target={{ server_ip }} port={{ port }}")
output = tmpl.render(server_ip="10.0.0.1", port=6514)

Paramiko SSH Deployment

import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname=host, username=user, key_filename=key)
sftp = client.open_sftp()
sftp.file(remote_path, "w").write(content)
client.exec_command("systemctl restart rsyslog")
client.close()

OpenSSL Certificate Generation

openssl req -x509 -newkey rsa:4096 -keyout ca-key.pem -out ca.pem -days 3650 -nodes
openssl req -newkey rsa:2048 -keyout server-key.pem -out server.csr -nodes
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem

Scripts 1

agent.py8.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Rsyslog Centralization Agent - Generates and deploys TLS-secured rsyslog configurations."""

import json
import logging
import argparse
import subprocess
from datetime import datetime

from jinja2 import Template

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

SERVER_TEMPLATE = Template("""\
# Rsyslog Server Configuration - TLS Centralized Logging
# Generated by Syslog Centralization Agent

# Load modules
module(load="imuxsock")
module(load="imklog")
module(load="imtcp"
    StreamDriver.Name="gtls"
    StreamDriver.Mode="1"
    StreamDriver.Authmode="x509/name"
    PermittedPeer=["{{ permitted_peers | join('","') }}"]
)

# TLS Certificate Configuration
global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="{{ ca_cert }}"
    DefaultNetstreamDriverCertFile="{{ server_cert }}"
    DefaultNetstreamDriverKeyFile="{{ server_key }}"
)

# TLS Listener
input(type="imtcp" port="{{ tls_port }}")

# Templates
template(name="PerHostDir" type="string"
    string="/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log")

template(name="JSONFormat" type="string"
    string='{"timestamp":"%TIMESTAMP:::date-rfc3339%","host":"%HOSTNAME%","facility":"%syslogfacility-text%","severity":"%syslogseverity-text%","program":"%PROGRAMNAME%","msg":"%msg:::json%"}\\n')

template(name="PerHostJSON" type="string"
    string="/var/log/remote/%HOSTNAME%/json/%PROGRAMNAME%.json")

# Rules - Store per-host with standard format
*.* ?PerHostDir

# Also store in JSON format for SIEM ingestion
*.* ?PerHostJSON;JSONFormat

# High-severity alerts to dedicated file
*.err /var/log/remote/errors.log
""")

CLIENT_TEMPLATE = Template("""\
# Rsyslog Client Configuration - TLS Forwarding
# Generated by Syslog Centralization Agent

# TLS Certificate Configuration
global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="{{ ca_cert }}"
    DefaultNetstreamDriverCertFile="{{ client_cert }}"
    DefaultNetstreamDriverKeyFile="{{ client_key }}"
)

# Forward all logs to central server with TLS and reliable queue
action(
    type="omfwd"
    target="{{ server_ip }}"
    port="{{ tls_port }}"
    protocol="tcp"
    StreamDriver="gtls"
    StreamDriverMode="1"
    StreamDriverAuthMode="x509/name"
    StreamDriverPermittedPeers="{{ server_ip }}"
    queue.type="LinkedList"
    queue.filename="fwdRule1"
    queue.maxdiskspace="{{ queue_disk_space }}"
    queue.saveonshutdown="on"
    queue.size="{{ queue_size }}"
    action.resumeRetryCount="-1"
    action.resumeInterval="30"
)
""")


def generate_server_config(server_ip, clients, ca_cert, server_cert, server_key, tls_port=6514):
    """Generate rsyslog server configuration with TLS."""
    config = SERVER_TEMPLATE.render(
        permitted_peers=clients + [server_ip],
        ca_cert=ca_cert,
        server_cert=server_cert,
        server_key=server_key,
        tls_port=tls_port,
    )
    logger.info("Generated server config for %s with %d permitted peers", server_ip, len(clients))
    return config


def generate_client_config(server_ip, ca_cert, client_cert, client_key, tls_port=6514):
    """Generate rsyslog client configuration with TLS forwarding."""
    config = CLIENT_TEMPLATE.render(
        server_ip=server_ip,
        ca_cert=ca_cert,
        client_cert=client_cert,
        client_key=client_key,
        tls_port=tls_port,
        queue_disk_space="1g",
        queue_size="50000",
    )
    logger.info("Generated client config forwarding to %s:%d", server_ip, tls_port)
    return config


def generate_tls_certificates(output_dir, server_cn, client_cns):
    """Generate CA, server, and client TLS certificates using OpenSSL."""
    ca_key = f"{output_dir}/ca-key.pem"
    ca_cert = f"{output_dir}/ca.pem"
    subprocess.run([
        "openssl", "req", "-x509", "-newkey", "rsa:4096", "-keyout", ca_key,
        "-out", ca_cert, "-days", "3650", "-nodes",
        "-subj", f"/CN=Syslog CA/O=SOC/C=US",
    ], capture_output=True, check=True, timeout=120)
    logger.info("Generated CA certificate: %s", ca_cert)

    for cn in [server_cn] + client_cns:
        key_file = f"{output_dir}/{cn}-key.pem"
        cert_file = f"{output_dir}/{cn}-cert.pem"
        csr_file = f"{output_dir}/{cn}.csr"
        subprocess.run([
            "openssl", "req", "-newkey", "rsa:2048", "-keyout", key_file,
            "-out", csr_file, "-nodes", "-subj", f"/CN={cn}/O=SOC/C=US",
        ], capture_output=True, check=True, timeout=120)
        subprocess.run([
            "openssl", "x509", "-req", "-in", csr_file, "-CA", ca_cert,
            "-CAkey", ca_key, "-CAcreateserial", "-out", cert_file, "-days", "365",
        ], capture_output=True, check=True, timeout=120)
        logger.info("Generated certificate for %s", cn)
    return ca_cert


def deploy_config_ssh(host, config_content, remote_path, username="root", key_file=None):
    """Deploy rsyslog configuration to a remote host via SSH."""
    import paramiko
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    connect_kwargs = {"hostname": host, "username": username}
    if key_file:
        connect_kwargs["key_filename"] = key_file
    client.connect(**connect_kwargs)
    sftp = client.open_sftp()
    with sftp.file(remote_path, "w") as f:
        f.write(config_content)
    sftp.close()
    _, stdout, stderr = client.exec_command("systemctl restart rsyslog")
    exit_status = stdout.channel.recv_exit_status()
    client.close()
    logger.info("Deployed config to %s:%s (restart exit: %d)", host, remote_path, exit_status)
    return exit_status == 0


def validate_tls_connection(server_ip, tls_port=6514, ca_cert=None):
    """Validate TLS connectivity to the rsyslog server."""
    cmd = [
        "openssl", "s_client", "-connect", f"{server_ip}:{tls_port}",
        "-CAfile", ca_cert or "/etc/ssl/certs/ca-certificates.crt",
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=10, input="")
        connected = "Verify return code: 0" in result.stdout
        logger.info("TLS validation to %s:%d: %s", server_ip, tls_port, "OK" if connected else "FAILED")
        return connected
    except subprocess.TimeoutExpired:
        return False


def generate_report(server_config, client_configs, deployments, tls_valid):
    """Generate syslog centralization deployment report."""
    report = {
        "timestamp": datetime.utcnow().isoformat(),
        "server_config_generated": bool(server_config),
        "client_configs_generated": len(client_configs),
        "deployments": deployments,
        "tls_validated": tls_valid,
    }
    print(f"SYSLOG REPORT: {len(client_configs)} client configs, TLS: {'OK' if tls_valid else 'PENDING'}")
    return report


def main():
    parser = argparse.ArgumentParser(description="Rsyslog Centralization Agent")
    parser.add_argument("--server-ip", required=True, help="Syslog server IP")
    parser.add_argument("--clients", required=True, help="Comma-separated client IPs")
    parser.add_argument("--ca-cert", default="/etc/rsyslog.d/ca.pem")
    parser.add_argument("--server-cert", default="/etc/rsyslog.d/server-cert.pem")
    parser.add_argument("--server-key", default="/etc/rsyslog.d/server-key.pem")
    parser.add_argument("--tls-port", type=int, default=6514)
    parser.add_argument("--deploy", action="store_true", help="Deploy configs via SSH")
    parser.add_argument("--config-dir", default="./rsyslog_configs")
    parser.add_argument("--output", default="syslog_report.json")
    args = parser.parse_args()

    clients = [c.strip() for c in args.clients.split(",")]
    import os
    os.makedirs(args.config_dir, exist_ok=True)

    server_config = generate_server_config(
        args.server_ip, clients, args.ca_cert, args.server_cert, args.server_key, args.tls_port
    )
    with open(f"{args.config_dir}/server.conf", "w") as f:
        f.write(server_config)

    client_configs = {}
    for client_ip in clients:
        config = generate_client_config(
            args.server_ip, args.ca_cert,
            f"/etc/rsyslog.d/{client_ip}-cert.pem",
            f"/etc/rsyslog.d/{client_ip}-key.pem",
            args.tls_port,
        )
        with open(f"{args.config_dir}/client-{client_ip}.conf", "w") as f:
            f.write(config)
        client_configs[client_ip] = config

    deployments = []
    if args.deploy:
        for client_ip, config in client_configs.items():
            ok = deploy_config_ssh(client_ip, config, "/etc/rsyslog.d/99-central.conf")
            deployments.append({"host": client_ip, "success": ok})

    tls_valid = validate_tls_connection(args.server_ip, args.tls_port, args.ca_cert)
    report = generate_report(server_config, client_configs, deployments, tls_valid)

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)
    logger.info("Report saved to %s", args.output)


if __name__ == "__main__":
    main()
Keep exploring