update opensearch

This commit is contained in:
Graham McIntire 2026-07-24 09:17:28 -05:00
parent d644868602
commit 7ff93a5ac3
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
15 changed files with 489 additions and 2 deletions

View file

@ -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 }}"

View file

@ -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

View file

@ -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}

View file

@ -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: ""

View file

@ -0,0 +1,6 @@
---
- name: Restart fluent-bit
ansible.builtin.systemd:
name: fluent-bit
state: restarted
daemon_reload: true

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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 ^(\<(?<pri>[0-9]+)\>)?((?<time>[A-Z][a-z]{2}\s+\d+\s+\d{2}:\d{2}:\d{2})\s+(?<host>\S+)\s+)?(?<ident>\S+)\s+(?<message>.*)$
Time_Key time
Time_Format %b %d %H:%M:%S
Time_Keep On

View file

@ -27,6 +27,17 @@ opensearch_discovery_type: single-node
# Plugins to install (empty by default)
opensearch_plugins: []
# --- Security Plugin ---
opensearch_security_enabled: true
opensearch_admin_username: admin
# bcrypt hash of the admin password — generate with:
# python3 -c "import bcrypt; print(bcrypt.hashpw(b'<password>', bcrypt.gensalt()).decode())"
# Override in host_vars or vault.
opensearch_admin_password_hash: ""
# Plaintext admin password (for Dashboards to authenticate to OpenSearch).
# Override in host_vars or vault.
opensearch_admin_password: ""
# Sysctl: vm.max_map_count required by OpenSearch
opensearch_vm_max_map_count: 262144

View file

@ -81,6 +81,55 @@
mode: "0640"
notify: Restart opensearch
- name: Check if demo TLS certs exist
ansible.builtin.stat:
path: "{{ opensearch_config_dir }}/{{ item }}"
loop:
- esnode.pem
- esnode-key.pem
- root-ca.pem
register: opensearch_demo_certs
when: opensearch_security_enabled | default(true)
- name: Generate self-signed TLS certs for OpenSearch
ansible.builtin.shell:
cmd: |
set -e
openssl req -x509 -newkey rsa:4096 \
-keyout {{ opensearch_config_dir }}/esnode-key.pem \
-out {{ opensearch_config_dir }}/esnode.pem \
-days 3650 -nodes \
-subj "/CN={{ opensearch_node_name }}" \
-addext "subjectAltName=DNS:localhost,DNS:{{ inventory_hostname }},IP:127.0.0.1"
cp {{ opensearch_config_dir }}/esnode.pem {{ opensearch_config_dir }}/root-ca.pem
chown {{ opensearch_user }}:{{ opensearch_group }} {{ opensearch_config_dir }}/esnode.pem {{ opensearch_config_dir }}/esnode-key.pem {{ opensearch_config_dir }}/root-ca.pem
chmod 600 {{ opensearch_config_dir }}/esnode-key.pem
creates: "{{ opensearch_config_dir }}/esnode.pem"
when:
- opensearch_security_enabled | default(true)
- (opensearch_demo_certs.results | selectattr('stat.exists', 'equalto', false) | list | length) > 0
notify: Restart opensearch
- name: Render internal_users.yml
ansible.builtin.template:
src: internal_users.yml.j2
dest: "{{ opensearch_config_dir }}/opensearch-security/internal_users.yml"
owner: "{{ opensearch_user }}"
group: "{{ opensearch_group }}"
mode: "0640"
when: opensearch_security_enabled | default(true)
notify: Restart opensearch
- name: Render roles_mapping.yml
ansible.builtin.template:
src: roles_mapping.yml.j2
dest: "{{ opensearch_config_dir }}/opensearch-security/roles_mapping.yml"
owner: "{{ opensearch_user }}"
group: "{{ opensearch_group }}"
mode: "0640"
when: opensearch_security_enabled | default(true)
notify: Restart opensearch
- name: Render jvm.options
ansible.builtin.template:
src: jvm.options.j2
@ -106,6 +155,25 @@
state: started
daemon_reload: true
- name: Create syslog index template
ansible.builtin.uri:
url: "https://127.0.0.1:{{ opensearch_http_port }}/_index_template/syslog"
method: PUT
user: "{{ opensearch_admin_username }}"
password: "{{ opensearch_admin_password }}"
force_basic_auth: true
validate_certs: false
body_format: json
body:
index_patterns: ["syslog-*"]
template:
settings:
number_of_shards: 1
number_of_replicas: 0
priority: 100
status_code: [200, 201]
when: opensearch_security_enabled | default(true)
# --- OpenSearch Dashboards ---
- name: Create opensearch-dashboards directories

View file

@ -0,0 +1,13 @@
# {{ ansible_managed | default('Managed by Ansible') }}
#
# OpenSearch Security — internal user database.
# Applied on security index initialization (plugins.security.allow_default_init_securityindex: true).
---
_meta:
type: "internalusers"
config_version: 2
{{ opensearch_admin_username }}:
hash: "{{ opensearch_admin_password_hash }}"
reserved: true
description: "Admin user — managed by Ansible"

View file

@ -6,6 +6,12 @@ server.host: 127.0.0.1
server.port: {{ opensearch_dashboards_port }}
opensearch.hosts:
- {{ opensearch_dashboards_opensearch_url }}
- https://127.0.0.1:{{ opensearch_http_port }}
{% if opensearch_security_enabled | default(true) %}
opensearch.username: {{ opensearch_admin_username }}
opensearch.password: {{ opensearch_admin_password }}
opensearch.ssl.verificationMode: none
{% endif %}
opensearch.requestTimeout: 30000

View file

@ -17,8 +17,26 @@ transport.port: {{ opensearch_transport_port }}
# Single-node discovery (no cluster formation needed)
discovery.type: {{ opensearch_discovery_type }}
# Security — disabled for internal-only binding on loopback
# Security — TLS with demo certs, internal user database
{% if opensearch_security_enabled | default(true) %}
plugins.security.disabled: false
{% if opensearch_security_allow_default_init | default(true) %}
plugins.security.allow_default_init_securityindex: true
{% endif %}
plugins.security.authcz.admin_dn:
- "CN={{ opensearch_node_name }}"
- "CN={{ inventory_hostname }}"
plugins.security.ssl.transport.pemcert_filepath: {{ opensearch_config_dir }}/esnode.pem
plugins.security.ssl.transport.pemkey_filepath: {{ opensearch_config_dir }}/esnode-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: {{ opensearch_config_dir }}/root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: {{ opensearch_config_dir }}/esnode.pem
plugins.security.ssl.http.pemkey_filepath: {{ opensearch_config_dir }}/esnode-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: {{ opensearch_config_dir }}/root-ca.pem
{% else %}
plugins.security.disabled: true
{% endif %}
# Performance
indices.memory.index_buffer_size: 20%

View file

@ -0,0 +1,48 @@
# {{ ansible_managed | default('Managed by Ansible') }}
#
# OpenSearch Security — role mappings.
# Maps users and backend roles to OpenSearch roles defined in roles.yml.
---
_meta:
type: "rolesmapping"
config_version: 2
all_access:
reserved: false
users:
- "{{ opensearch_admin_username }}"
backend_roles:
- "admin"
description: "Maps admin to all_access"
own_index:
reserved: false
users:
- "*"
description: "Allow full access to an index named like the username"
logstash:
reserved: false
backend_roles:
- "logstash"
kibana_user:
reserved: false
backend_roles:
- "kibanauser"
description: "Maps kibanauser to kibana_user"
readall:
reserved: false
backend_roles:
- "readall"
manage_snapshots:
reserved: false
backend_roles:
- "snapshotrestore"
kibana_server:
reserved: true
users:
- "kibanaserver"