※当サイトの記事には、広告・プロモーションが含まれます。

Linux VM間でsyslog メッセージの送受信をPythonで実現するのに必要な情報を整理してみる

www.itmedia.co.jp

 2月から取り沙汰されてきたネット証券への不正アクセス・不正取引を巡り、新たな騒動が巻き起こっている。4月30日にSBI証券が、多要素認証なしにログイン可能だった「バックアップサイト」の閉鎖を発表した他、5月1日、SNSで人気の投資家・テスタさんが証券口座を乗っ取られたと投稿。一連の出来事をきっかけとして、大手2社を中心に、ネット証券のセキュリティ的な危うさに批判が集中している。

ネット証券、セキュリティのずさんさ露呈 ID・パスワードだけで入れる「裏口」に批判 - ITmedia NEWS

 一連の騒ぎを受けてか、日本証券業協会も投資家への呼びかけを始めた。2日には、多要素認証の設定必須化を決定した証券会社69社の一覧を公表。「多要素認証の必須化は、投資家のセキュリティ対策として大変重要な措置となり、万が一、ログインID・パスワードを窃取されても、単要素認証と比べ被害を防止できる可能性が高まる」と注意喚起した。

ネット証券、セキュリティのずさんさ露呈 ID・パスワードだけで入れる「裏口」に批判 - ITmedia NEWS

www.itmedia.co.jp

 SMBC日興証券SBI証券楽天証券は5月2日、フィッシング詐欺などによる証券口座への不正アクセスと不正取引(第三者による売買)の被害について、一定の補償を行う方針をそれぞれ発表した。日本証券業協会(日証協)が同日公表した、証券会社10社による申し合わせを受けた対応となる。

“不正取引”被害への補償、各社の対応は? SBI証券・楽天証券は「対象顧客には月内に案内開始」 - ITmedia NEWS

 SMBC日興証券は同日、被害状況を精査した上で、個別の事情を踏まえて「被害補償の可否や内容を検討する」と発表。案内時期については明示していない。ID・パスワードの管理状況やセキュリティ設定などによっては「補償できない場合もある」として、ログイン時のワンタイムパスワードの設定といった対策を改めて呼び掛けている。

“不正取引”被害への補償、各社の対応は? SBI証券・楽天証券は「対象顧客には月内に案内開始」 - ITmedia NEWS

⇧ とりあえず、「全額補償」しないとか有り得ないんだが...

何と言うか、

ascii.jp

 仮想通貨取引所コインチェックで1月26日に発生した約580億円相当の仮想通貨流出事件を受け、SBIホールディングス代表の北尾吉孝社長が30日、運営元コインチェック社を「カス中のカス」と声を荒げて批判した。同日に開催した同社2018年3月期第3四半期決算説明会の中で。

ASCII.jp:SBI北尾社長コインチェックに激怒「カス中のカス」と猛批判

 北尾代表は、同件の問題点はウォレットに関して基本的なセキュリティー対策さえ講じていなかった「本当に初歩的な問題」であると指摘。同社がCMを出していたことにふれ「お金をかけなければいけないところ(セキュリティー)にお金をかけず客を集めるためにお金を使った。こういう輩はカス中のカス」と喝破した。

ASCII.jp:SBI北尾社長コインチェックに激怒「カス中のカス」と猛批判

⇧ 過去の発言を見るに、グループ企業であろう「SBI証券」については、「全額補償」すべきな気がしますな...

syslogとは?

Wikipediaによると、

syslogシスログ)は、ログメッセージIPネットワーク上で転送するための標準規格である。

syslog - Wikipedia

"syslog" という用語は、その通信プロトコルを指すだけでなく、syslog メッセージを送信するシステム(アプリケーションやライブラリ)syslog メッセージを受信し報告・分析するシステムに対しても使われる。

syslog - Wikipedia

syslogの各メッセージには、そのメッセージを生成したシステムの種類を示すファシリティコードが付与され、重大度が設定される。

syslog - Wikipedia

⇧ 用語の意味合いがファジーで辛い...

一応、ほとんどの「OS(Operation System)」で実装されているらしいが、実装に差異があるのかが分からない...

syslogの実装は、多くのオペレーティングシステムで行われている。 

syslog - Wikipedia

ネットワーク上で動作する場合、syslogはクライアントサーバモデルを採用している。受信側(サーバ)は一般に"syslogd"、"syslogデーモン"、"syslogサーバ" などと呼ばれる。クライアントは1024バイト以下の短いテキストメッセージをサーバに送信する。syslogメッセージはUDPまたはTCP上で送信される。

syslog - Wikipedia

送信されるデータは一般にクリアテキストであるが、Stunnel、sslio、sslwrap といった SSL ラッパーを使って SSL/TLS による暗号化が可能である

syslog - Wikipedia

⇧ ネットワーク経由の送受信にも対応しているらしく、「クライアントサーバーモデル」になるらしい。

「syslog」の送受信については、「TCP/IP モデル(TCP/IP参照モデル)」で考えた場合、

⇧ 4層(レイヤー)が関係してくることになるのだが、

blog.smartbuildingsacademy.com

www.geeksforgeeks.org

Working :
Syslog standard defines three layers i.e., the Syslog transport layer, Syslog application layer, and Syslog content layer.

  1. Syslog content layer –
    It is the actual data contained in the event message. It contains some informational elements such as the facility codes and severity levels.
  2. Syslog Application layer –
    This layer generates, interprets, routes, and stores the message.
  3. Syslog Transport layer –
    This layer transmits the message over a network.

https://www.geeksforgeeks.org/what-is-syslog-server-and-its-working/

⇧ 主に、

  1. Application Layer
  2. Transport Layer

が「アプリケーション」側で意識する必要がありそうということみたい。

送信するデータのフォーマットについては、

メッセージ

RFC 3164では、メッセージ・コンポーネント(measge component、MSGと略称される)は、メッセージを生成したプログラムやプロセスの名前であるTAGと、メッセージの詳細を含むCONTENTの2つのフィールドで構成されると規定されている。

syslog - Wikipedia

RFC 5424には、「MSGは、RFC 3164でCONTENTと呼ばれていたものである。TAGはヘッダの一部になったが、単一のフィールドではない。TAGはAPP-NAME、PROCID、MSGIDに分割されている。これはTAGの使い方と完全には似ていないが、ほとんどの場合同じ機能を提供する」と書かれている。rsyslog英語版などの一般的なシスログツールは、この新規格に準拠している。

syslog - Wikipedia

RFCが存在するらしく、

  1. RFC 3164
  2. RFC 5424

の2つが定義されているらしいのだが、

制限

プロセス、アプリケーション、オペレーティングシステムはそれぞれ独立して記述されているため、ログメッセージの内容にはほとんど統一性がない。フォーマットや内容については、何の想定もされていない。

syslog - Wikipedia

syslogメッセージはフォーマットされているが(RFC 5424にはABNF(拡張バッカス・ナウア記法)の定義がある)、そのMSGフィールドはフォーマットされていない。

syslog - Wikipedia

syslogのプロトコルは片方向通信であり、受信側が受信できたことを発信側が確認する手段はない。

syslog - Wikipedia

⇧ 実際には「フォーマット」が統一されていないらしく、「RFC」 の定義がほぼ意味ない...

ちなみに、「通信プロトコル」については、

通信プロトコル

ネットワーク上で動作する場合、syslogはクライアントサーバモデルを採用しており、サーバはクライアントからのプロトコル要求をwell-knownポートまたは予約済みポートで待ち受ける。歴史的には、ネットワークログの用途で使用される最も一般的なトランスポート層プロトコルUDPであり、サーバの待受けポートは514である

syslog - Wikipedia

UDPには輻輳制御カニズムがないため、実装にはTransport Layer Security(TLS)のサポートが必要であり、一般的な使用にはTCPのポート6514が推奨される

syslog - Wikipedia

⇧ とあることから、「クライアント」と「サーバー」間で「OS(Operation System)」による差異は無いということかと。

とりあえず、「メッセージ」の「フォーマット」が統一されていないということで、「クライアント」側と「サーバー」側で、どんなデータを許容するかについて認識を合わせておかないと、「サーバー」側で受信した時に何某かの処理を行う時に対処しようが無い気がする...

RFC 3164とRFC 5424の送信項目を整理してみる

ネットの情報を漁っていたところ、

www.oresamalabo.net

⇧ 上記サイト様によりますと、上記のような構造になっているらしく、

  1. syslogメッセージ
    1. Syslog header
    2. Message

という形になるそうな。

■Syslog header

■Message

⇧ といった感じらしいのだが、実際には、送受信はUDPになるようなので、

milestone-of-se.nesuke.com

syslog のパケットフォーマットは 2 種類あります。

1 つがBSD 形式 (RFC3164)、もう 1 つはIETF 形式 (RFC5424) です。

【図解】syslogプロトコル再入門 ~フォーマット(BSD/IETF形式),Facility/Severity一覧,Ciscoの設定~ | SEの道標

RFC 3164

RFC 5424

⇧ といった感じで、「パケット」の「データ」部分に「syslog メッセージ」が格納される感じになりますと。

User Datagram Protocol(ユーザ データグラム プロトコルUDP)はIPネットワーク上のアプリケーション間データグラム送信を実現する通信プロトコルである

User Datagram Protocol - Wikipedia

データ長

データ長Length)はユーザーデータグラム(UDPヘッダ + データ)のオクテット数である。

User Datagram Protocol - Wikipedia

データ長はUDPヘッダの必須フィールドである。UDPヘッダが8バイト固定であるため最小値は 8 である。理論上の上限は65,535バイト(ヘッダ8バイト + データ65,527バイト)である。下位層がIPv4の場合、実際の上限は65,507バイト(IPヘッダ20バイトとUDPヘッダ8バイトを差し引いた値)となる。

User Datagram Protocol - Wikipedia

IPv6のジャンボグラム機能では、65,535バイトを越えるサイズのUDPパケットを扱える。この場合、IPv6のオプションヘッダでサイズを指定し、最大4,294,967,295バイト(232 - 1)を指定できるので、ヘッダ部の8バイトを差し引くと最大4,294,967,287バイトのデータを扱える。

User Datagram Protocol - Wikipedia

⇧「データ」部分の最大が、

No Internet Protocol データの最大値 syslog メッセージ データの最大値
1 IPv4 65,507バイト RFC 3164 1024 byte未満
2 RFC 5424 2048 byte未満
2 IPv6 4,294,967,287バイト RFC 3164 1024 byte未満
4 RFC 5424 2048 byte未満

となるようなので、IPv4のケースで考えた場合、以下のようになるらしい。

RFC 3164

構成 user datagram syslogメッセージ
UDP byte 構造(論理) byte 構造 byte 項目 byte
UDP header Source Port 2 - - - - - -
Destination Port 2 - - - - - -
Length 2 - - - - - -
Checksum 2 - - - - - -
data 65507 syslog header ※1 PRI ※2   - -
Header ※3 - TIMESTAMP ※4 -
HOSTNAME -
Message ※1 MSG ※5 - TAG ※6 -
CONTENT ※7 -

※1 「syslog header」と「Message」合わせて、1024 byte 以下。

※2 以下のフォーマットのASCII文字列。詳細については、RFC 3164を参照。

< + Facility × 8 + Seventy + >   

※3 以下のフォーマットのASCII文字列。詳細については、RFC 3164を参照。

TIMESTAMP + 半角スペース + HOSTNAME  

※4 以下のフォーマットのASCII文字列。詳細については、RFC 3164を参照。

MMM DD HH:MM:SS

※5 以下のフォーマットのASCII文字列になることが多いらしい。詳細については、RFC 3164を参照。

[TAG]: CONTENT

※6 プロセス名やプログラム名。

※7 ログの内容。

定義がファジー過ぎて非常に困るのだが...

とりあえず、「RFC 3164」の「syslog」のフォーマットにおいては「TIMESTAMP」については、「タイムゾーン」を設定できないので、「syslog」の送信先のマシンの「タイムゾーン」と異なる場合が起こり得るので、送信先のマシン(「syslog」を受信する側)で何某かのチェック処理を行う必要がありますと。

まぁ、「RFC 3164」に準拠していなかったとしても、「UDP」の「data」として不正な形になっていないのであれば、普通に送受信できてしまう気がするので、チェック処理が重要そうな気はするのだが、言及されている情報が少ないんよね...

PythonRFC 5424のsyslogのフォーマットを作成する

Python」の公式のドキュメントの「サンプル」で、

docs.python.org

⇧「RFC 5424」についての見本があったので、ChatGPT氏に「型」を付与してもらった。

Python」の標準の「モジュール」である「logging」は、「RFC 3164」をデフォルトでサポートしていると考えて良いのかが分からないのだが...

■syslog部分のフォーマット作成

■■RFC 5424

■■D:\work-soft\python\auto_translate\src\syslog_rfc5424_formatter.py

import datetime
import logging
import logging.handlers
import re
import socket
import time
from typing import Any, Optional, Dict, Callable

class SysLogHandler5424(logging.handlers.SysLogHandler):
    tz_offset: re.Pattern[str] = re.compile(r'([+-]\d{2})(\d{2})$')
    escaped: re.Pattern[str] = re.compile(r'([\]"\\])')

    def __init__(
        self,
        *args: Any,
        msgid: Optional[str] = None,
        appname: Optional[str] = None,
        **kwargs: Any
    ) -> None:
        self.msgid = msgid
        self.appname = appname
        super().__init__(*args, **kwargs)

    def format(self, record: logging.LogRecord) -> str:
        version: int = 1
        asctime: str = datetime.datetime.fromtimestamp(record.created).isoformat()

        m: Optional[re.Match[str]] = self.tz_offset.match(time.strftime('%z'))
        has_offset: bool = False

        if m and time.timezone:
            hrs, mins = m.groups()
            if int(hrs) or int(mins):
                has_offset = True

        if not has_offset:
            asctime += 'Z'
        else:
            asctime += f'{hrs}:{mins}'

        try:
            hostname: str = socket.gethostname()
        except Exception:
            hostname = '-'

        appname: str = self.appname or '-'
        procid: int = record.process
        msgid: str = '-'
        msg: str = super().format(record)
        sdata: str = '-'

        if hasattr(record, 'structured_data'):
            sd: Dict[str, Dict[str, Any]] = record.structured_data
            parts: list[str] = []

            def replacer(m: re.Match[str]) -> str:
                g = m.groups()
                return '\\' + g[0]

            for sdid, dv in sd.items():
                part: str = f'[{sdid}'
                for k, v in dv.items():
                    s: str = str(v)
                    s = self.escaped.sub(replacer, s)
                    part += f' {k}="{s}"'
                part += ']'
                parts.append(part)

            sdata = ''.join(parts)

        return f'{version} {asctime} {hostname} {appname} {procid} {msgid} {sdata} {msg}'

■■RFC 3164

■■D:\work-soft\python\auto_translate\src\syslog_rfc3164_formatter.py

import logging
import socket
import time
from typing import Optional

class RFC3164Formatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        severity: int = getattr(record, 'severity', record.levelno % 8)
        facility: int = getattr(record, 'facility', 1)  # default facility: user-level messages

        pri: int = facility * 8 + severity
        timestamp: str = time.strftime('%b %d %H:%M:%S', time.localtime(record.created))
        hostname: str = socket.gethostname()
        tag: str = getattr(record, 'tag', record.name)
        pid: Optional[int] = getattr(record, 'process', None)

        pid_part: str = f"[{pid}]" if pid is not None else ""
        message: str = super().format(record)

        return f"<{pri}>{timestamp} {hostname} {tag}{pid_part}: {message}"
    

⇧ といった感じ。

■syslog部分のフォーマット確認

■■RFC 5424

■■D:\work-soft\python\auto_translate\src\create_rfc5424.py

import logging
import time
from syslog_rfc5424_formatter import SysLogHandler5424

# ハンドラを生成(送信先は仮でOK)
handler = SysLogHandler5424(address=('localhost', 514), appname='myapp', msgid='TESTID')

# structured_data を含める
extra = {
    'structured_data': {
        'exampleSDID@32473': {
            'eventID': '1011',
            'eventSource': 'application',
            'user': 'alice'
        }
    }
}

# ダミーの LogRecord を作成
record = logging.LogRecord(
    name="mylogger",
    level=logging.INFO,
    pathname=__file__,
    lineno=123,
    msg="This is a test log for RFC5424.",
    args=(),
    exc_info=None
)

# structured_data を LogRecord に追加
record.structured_data = extra['structured_data']
record.process = 4321
record.created = time.time()

# フォーマットだけ確認
formatted = handler.format(record)
print("🧾 RFC5424 Formatted Log:\n", formatted)

■■RFC 3164

■■D:\work-soft\python\auto_translate\src\create_rfc3164.py

import logging
from logging import Logger
from syslog_rfc3164_formatter import RFC3164Formatter

def setup_logger() -> Logger:
    logger: Logger = logging.getLogger("test_formatter")
    logger.setLevel(logging.DEBUG)

    stream_handler: logging.StreamHandler = logging.StreamHandler()
    stream_handler.setFormatter(RFC3164Formatter('%(message)s'))
    logger.addHandler(stream_handler)

    return logger

def main() -> None:
    logger: Logger = setup_logger()

    for severity in range(8):  # type: int
        for facility in range(8):  # type: int
            extra: dict[str, int | str] = {
                'severity': severity,
                'facility': facility,
                'tag': 'test_app'
            }
            logger.info(
                f"Test log: severity={severity}, facility={facility}",
                extra=extra
            )

if __name__ == "__main__":
    main()

⇧ といった感じで、

  1. syslog メッセージ
    1. Syslog header
    2. Message

はできたと。

Linux VM間でsyslog メッセージの送受信をPythonで実現するのに必要な情報を整理してみる

本当は、

■syslog送受信(Cisco Router → Linux VM

Cisco Router → Linux VM

⇧ を検証したいのだが、「Cisco Router」は用意できないので、

■syslog送受信(Linux VMLinux VM

Linux VM → Linux VM

⇧ で検討してみることに。

送受信部分の処理についても、ChatGPT氏に質問したところ、

■送信側

# src/syslog_sender.py
import socket

def send_syslog_message(message: str, host: str = "127.0.0.1", port: int = 514, use_tcp: bool = False) -> None:
    """Syslog メッセージを送信(RFC3164 / RFC5424 対応)"""
    if use_tcp:
        with socket.create_connection((host, port)) as sock:
            sock.sendall(message.encode("utf-8"))
    else:
        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
            sock.sendto(message.encode("utf-8"), (host, port))

■受信側

# src/syslog_receiver.py
import socket
from typing import Tuple

def run_syslog_udp_receiver(host: str = "0.0.0.0", port: int = 514) -> None:
    """UDPでSyslogメッセージを受信"""
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        sock.bind((host, port))
        print(f"🟢 Syslog UDP server running on {host}:{port}\n")

        while True:
            data: bytes
            addr: Tuple[str, int]
            data, addr = sock.recvfrom(4096)
            print(f"📩 From {addr}: {data.decode('utf-8').strip()}")
    

⇧ 上記のような回答が返って来ましたと。

とりあえず、

gigazine.net

⇧ ChatGPT氏には、 セキュリティという概念は無いらしい...

By the way、

docs.python.org

UnixDatagramServer は UDPServer から派生していて、 UnixStreamServer からではないことに注意してください --- IP と Unix サーバの唯一の違いはアドレスファミリーです。

https://docs.python.org/ja/3.13/library/socketserver.html

⇧ 上記のページの内容によると、処理に時間のかからない「同期処理」を実現するケースにおいては、「TCP」や「UDP」を利用した通信で受信を担う「サーバー」用のクラスが「Python」の標準の「モジュール」で用意されているそうな。

 

「syslog」のIF(InterFace)仕様書と正確なサンプルが欲しいところですな...

そもそも、「syslog」の用語の定義がファジー過ぎる問題を何とかして欲しいのだが...

毎度モヤモヤ感が半端ない…

今回はこのへんで。