diff --git a/ansible/host_vars/logs.vntx.net.yml b/ansible/host_vars/logs.vntx.net.yml index 1ef7fa8..5c19179 100644 --- a/ansible/host_vars/logs.vntx.net.yml +++ b/ansible/host_vars/logs.vntx.net.yml @@ -7,6 +7,10 @@ ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new' opensearch_heap_min: "4g" opensearch_heap_max: "4g" +# OpenSearch security — admin credentials +opensearch_admin_password_hash: "$2b$12$gp3Lk2cCejFzjfacq1Ax0OHapD5XOUNqMN2TtzmA7EMpcZ6wcB71a" +opensearch_admin_password: "1QW8gf8DNfqfkC1saSiw" + # Firewall — Caddy needs HTTP/HTTPS open for Let's Encrypt + traffic firewall_allow_rules: - port: 80 @@ -15,3 +19,30 @@ firewall_allow_rules: - port: 443 proto: tcp comment: HTTPS + - port: 514 + proto: udp + from_ip: 204.110.188.0/22 + comment: Syslog UDP (VNTX) + - port: 514 + proto: udp + from_ip: 10.0.0.0/8 + comment: Syslog UDP (home LAN) + - port: 514 + proto: udp + from_ip: 100.64.0.0/10 + comment: Syslog UDP (Tailscale) + - port: 514 + proto: tcp + from_ip: 204.110.188.0/22 + comment: Syslog TCP (VNTX) + - port: 514 + proto: tcp + from_ip: 10.0.0.0/8 + comment: Syslog TCP (home LAN) + - port: 514 + proto: tcp + from_ip: 100.64.0.0/10 + comment: Syslog TCP (Tailscale) + +# Fluent Bit — uses same admin credentials as Dashboards +fluentbit_opensearch_password: "{{ opensearch_admin_password }}" diff --git a/ansible/playbook.yml b/ansible/playbook.yml index 8f2b763..9ff6102 100644 --- a/ansible/playbook.yml +++ b/ansible/playbook.yml @@ -410,3 +410,13 @@ - logs roles: - opensearch + +- name: Install and configure Fluent Bit syslog collector + hosts: opensearch_servers + become: true + gather_facts: true + tags: + - fluentbit + - logs + roles: + - fluentbit diff --git a/ansible/roles/caddy/templates/Caddyfile-logs.vntx.net.j2 b/ansible/roles/caddy/templates/Caddyfile-logs.vntx.net.j2 index c41f6e0..19ec567 100644 --- a/ansible/roles/caddy/templates/Caddyfile-logs.vntx.net.j2 +++ b/ansible/roles/caddy/templates/Caddyfile-logs.vntx.net.j2 @@ -1,4 +1,19 @@ {{ opensearch_caddy_domain | default(inventory_hostname) }} { + @os_denied { + path /os/* + not remote_ip 10.0.0.0/8 100.64.0.0/10 204.110.188.0/22 + } + respond @os_denied 403 + + # OpenSearch API — restricted to internal subnets + handle_path /os/* { + reverse_proxy https://127.0.0.1:9200 { + transport http { + tls_insecure_skip_verify + } + } + } + reverse_proxy 127.0.0.1:{{ opensearch_dashboards_port | default('5601') }} { header_up X-Forwarded-Host {host} header_up X-Real-IP {remote_host} diff --git a/ansible/roles/fluentbit/defaults/main.yml b/ansible/roles/fluentbit/defaults/main.yml new file mode 100644 index 0000000..c394347 --- /dev/null +++ b/ansible/roles/fluentbit/defaults/main.yml @@ -0,0 +1,30 @@ +--- +# Fluent Bit — lightweight syslog collector for logs.vntx.net +# Installed via official apt repo, configured for RFC 3164 syslog (MikroTik). + +# --- Package --- +fluentbit_package: fluent-bit + +# --- Repo --- +fluentbit_apt_key_url: "https://packages.fluentbit.io/fluentbit.key" +fluentbit_apt_keyring: /etc/apt/keyrings/fluentbit.gpg + +# --- User --- +fluentbit_user: fluentbit +fluentbit_group: fluentbit + +# --- Paths --- +fluentbit_config_dir: /etc/fluent-bit +fluentbit_log_dir: /var/log/fluent-bit + +# --- Syslog Input --- +fluentbit_syslog_port: 514 +fluentbit_syslog_bind: "0.0.0.0" + +# --- OpenSearch Output --- +fluentbit_opensearch_host: "127.0.0.1" +fluentbit_opensearch_port: 9200 +fluentbit_opensearch_index: syslog +fluentbit_opensearch_user: "{{ opensearch_admin_username | default('admin') }}" +# Plaintext password for Fluent Bit → OpenSearch — override in host_vars or vault. +fluentbit_opensearch_password: "" diff --git a/ansible/roles/fluentbit/handlers/main.yml b/ansible/roles/fluentbit/handlers/main.yml new file mode 100644 index 0000000..bcafdfb --- /dev/null +++ b/ansible/roles/fluentbit/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart fluent-bit + ansible.builtin.systemd: + name: fluent-bit + state: restarted + daemon_reload: true diff --git a/ansible/roles/fluentbit/tasks/main.yml b/ansible/roles/fluentbit/tasks/main.yml new file mode 100644 index 0000000..4fff990 --- /dev/null +++ b/ansible/roles/fluentbit/tasks/main.yml @@ -0,0 +1,139 @@ +--- +# Installs and configures Fluent Bit to receive syslog (RFC 3164) on UDP/TCP +# port 514 and forward to the local OpenSearch instance. + +- name: Ensure /etc/apt/keyrings exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + owner: root + group: root + mode: "0755" + +- name: Download Fluent Bit apt signing key + ansible.builtin.get_url: + url: "{{ fluentbit_apt_key_url }}" + dest: /tmp/fluentbit.key + mode: "0644" + +- name: Dearmor and install Fluent Bit apt key + ansible.builtin.shell: + cmd: gpg --dearmor < /tmp/fluentbit.key > {{ fluentbit_apt_keyring }} + creates: "{{ fluentbit_apt_keyring }}" + +- name: Add Fluent Bit apt repository + ansible.builtin.deb822_repository: + name: fluentbit + types: [deb] + uris: https://packages.fluentbit.io/ubuntu/noble + suites: [noble] + components: [main] + signed_by: "{{ fluentbit_apt_keyring }}" + state: present + +- name: Install fluent-bit + ansible.builtin.apt: + name: "{{ fluentbit_package }}" + state: present + update_cache: true + +- name: Stop and disable packaged fluent-bit service + ansible.builtin.systemd: + name: fluent-bit + state: stopped + enabled: false + failed_when: false + +- name: Create fluentbit group + ansible.builtin.group: + name: "{{ fluentbit_group }}" + system: true + state: present + +- name: Create fluentbit user + ansible.builtin.user: + name: "{{ fluentbit_user }}" + group: "{{ fluentbit_group }}" + system: true + shell: /usr/sbin/nologin + home: "{{ fluentbit_log_dir }}" + create_home: false + state: present + +- name: Create fluentbit directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ fluentbit_user }}" + group: "{{ fluentbit_group }}" + mode: "0750" + loop: + - "{{ fluentbit_config_dir }}" + - "{{ fluentbit_log_dir }}" + +- name: Render parsers.conf + ansible.builtin.template: + src: parsers.conf.j2 + dest: "{{ fluentbit_config_dir }}/parsers.conf" + owner: root + group: "{{ fluentbit_group }}" + mode: "0640" + notify: Restart fluent-bit + +- name: Render fluent-bit.conf + ansible.builtin.template: + src: fluent-bit.conf.j2 + dest: "{{ fluentbit_config_dir }}/fluent-bit.conf" + owner: root + group: "{{ fluentbit_group }}" + mode: "0640" + notify: Restart fluent-bit + +- name: Install fluent-bit systemd unit + ansible.builtin.template: + src: fluent-bit.service.j2 + dest: /etc/systemd/system/fluent-bit.service + owner: root + group: root + mode: "0644" + notify: Restart fluent-bit + +- name: Enable and start fluent-bit + ansible.builtin.systemd: + name: fluent-bit + enabled: true + state: started + daemon_reload: true + +- name: Create syslog-* index pattern in Dashboards + ansible.builtin.uri: + url: "http://127.0.0.1:{{ opensearch_dashboards_port | default('5601') }}/api/saved_objects/index-pattern/syslog-*" + method: POST + user: "{{ fluentbit_opensearch_user }}" + password: "{{ fluentbit_opensearch_password }}" + force_basic_auth: true + headers: + osd-xsrf: "true" + Content-Type: "application/json" + body_format: json + body: + attributes: + title: "syslog-*" + timeFieldName: "@timestamp" + status_code: [200, 201, 409] + +- name: Set syslog-* as default index in Dashboards + ansible.builtin.uri: + url: "http://127.0.0.1:{{ opensearch_dashboards_port | default('5601') }}/api/saved_objects/config/{{ opensearch_dashboards_version | default('2.19.6') }}" + method: PUT + user: "{{ fluentbit_opensearch_user }}" + password: "{{ fluentbit_opensearch_password }}" + force_basic_auth: true + headers: + osd-xsrf: "true" + Content-Type: "application/json" + body_format: json + body: + attributes: + defaultIndex: "syslog-*" + status_code: [200, 409] diff --git a/ansible/roles/fluentbit/templates/fluent-bit.conf.j2 b/ansible/roles/fluentbit/templates/fluent-bit.conf.j2 new file mode 100644 index 0000000..1da4702 --- /dev/null +++ b/ansible/roles/fluentbit/templates/fluent-bit.conf.j2 @@ -0,0 +1,40 @@ +# {{ ansible_managed | default('Managed by Ansible') }} +# Fluent Bit configuration for logs.vntx.net +# Receives RFC 3164 syslog from MikroTik, Ubiquiti, Cambium, etc. + +[SERVICE] + Flush 5 + Daemon off + Log_Level info + parsers_file {{ fluentbit_config_dir }}/parsers.conf + +[INPUT] + Name syslog + Listen {{ fluentbit_syslog_bind }} + Port {{ fluentbit_syslog_port }} + Mode udp + parser syslog-loose + raw_message_key raw + +[INPUT] + Name syslog + Listen {{ fluentbit_syslog_bind }} + Port {{ fluentbit_syslog_port }} + Mode tcp + parser syslog-loose + raw_message_key raw + +[OUTPUT] + Name opensearch + Match * + Host {{ fluentbit_opensearch_host }} + Port {{ fluentbit_opensearch_port }} + Index {{ fluentbit_opensearch_index }} + HTTP_User ${FLUENTBIT_OPENSEARCH_USER} + HTTP_Passwd ${FLUENTBIT_OPENSEARCH_PASSWORD} + tls On + tls.verify Off + Suppress_Type_Name On + Logstash_Format On + Logstash_Prefix {{ fluentbit_opensearch_index }} + Retry_Limit false diff --git a/ansible/roles/fluentbit/templates/fluent-bit.service.j2 b/ansible/roles/fluentbit/templates/fluent-bit.service.j2 new file mode 100644 index 0000000..aea7566 --- /dev/null +++ b/ansible/roles/fluentbit/templates/fluent-bit.service.j2 @@ -0,0 +1,41 @@ +# {{ ansible_managed | default('Managed by Ansible') }} +# Fluent Bit systemd unit — overrides the packaged unit to add +# AmbientCapabilities for privileged-port binding and credential env vars. + +[Unit] +Description=Fluent Bit syslog collector +Documentation=https://docs.fluentbit.io/ +After=network-online.target opensearch.service +Wants=network-online.target +Requires=opensearch.service + +[Service] +Type=simple +User={{ fluentbit_user }} +Group={{ fluentbit_group }} + +Environment=FLUENTBIT_OPENSEARCH_USER={{ fluentbit_opensearch_user }} +Environment=FLUENTBIT_OPENSEARCH_PASSWORD={{ fluentbit_opensearch_password }} + +ExecStart=/opt/fluent-bit/bin/fluent-bit \ + --config={{ fluentbit_config_dir }}/fluent-bit.conf + +Restart=on-failure +RestartSec=5 +LimitNOFILE=65536 + +# Bind to privileged port 514 without running as root +AmbientCapabilities=CAP_NET_BIND_SERVICE +CapabilityBoundingSet=CAP_NET_BIND_SERVICE + +NoNewPrivileges=true +ProtectHome=true +ProtectSystem=full +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +PrivateTmp=true +ReadWritePaths={{ fluentbit_log_dir }} + +[Install] +WantedBy=multi-user.target diff --git a/ansible/roles/fluentbit/templates/parsers.conf.j2 b/ansible/roles/fluentbit/templates/parsers.conf.j2 new file mode 100644 index 0000000..86d667e --- /dev/null +++ b/ansible/roles/fluentbit/templates/parsers.conf.j2 @@ -0,0 +1,11 @@ +# {{ ansible_managed | default('Managed by Ansible') }} +# Syslog parser for mixed network gear (MikroTik, Ubiquiti, Cambium, etc.) +# Handles both standard RFC 3164 and bare/partial formats. + +[PARSER] + Name syslog-loose + Format regex + Regex ^(\<(?[0-9]+)\>)?((?