From 85cecf78f6313ba2f756285dc1769da4617f3710 Mon Sep 17 00:00:00 2001 From: Ruslan Piatrovich Date: Thu, 4 Jun 2026 11:27:44 +0300 Subject: [PATCH] Add Salt baseline states for Proxmox Mail Gateway. Co-authored-by: Cursor --- .gitignore | 13 +++ .yamllint | 16 ++++ Makefile | 38 ++++++++ README.md | 92 +++++++++++++++++++- pillar/pmg.sls | 63 ++++++++++++++ pillar/top.sls | 3 + pmg/audit_logging.sls | 56 ++++++++++++ pmg/auto_updates.sls | 28 ++++++ pmg/common.sls | 5 ++ pmg/custom_check.sls | 111 ++++++++++++++++++++++++ pmg/files/20auto-upgrades.jinja | 4 + pmg/files/50unattended-upgrades.jinja | 14 +++ pmg/files/99-hardening.conf.jinja | 10 +++ pmg/files/99-pmg-baseline.rules.jinja | 14 +++ pmg/files/journald-retention.conf.jinja | 3 + pmg/files/pmg-custom-check.jinja | 49 +++++++++++ pmg/init.sls | 8 ++ pmg/pmg_baseline.sls | 31 +++++++ pmg/security_agents.sls | 69 +++++++++++++++ pmg/ssh_hardening.sls | 35 ++++++++ pmg/update.sls | 50 +++++++++++ pmg/verify.sls | 15 ++++ top.sls | 3 + 23 files changed, 729 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .yamllint create mode 100644 Makefile create mode 100644 pillar/pmg.sls create mode 100644 pillar/top.sls create mode 100644 pmg/audit_logging.sls create mode 100644 pmg/auto_updates.sls create mode 100644 pmg/common.sls create mode 100644 pmg/custom_check.sls create mode 100644 pmg/files/20auto-upgrades.jinja create mode 100644 pmg/files/50unattended-upgrades.jinja create mode 100644 pmg/files/99-hardening.conf.jinja create mode 100644 pmg/files/99-pmg-baseline.rules.jinja create mode 100644 pmg/files/journald-retention.conf.jinja create mode 100644 pmg/files/pmg-custom-check.jinja create mode 100644 pmg/init.sls create mode 100644 pmg/pmg_baseline.sls create mode 100644 pmg/security_agents.sls create mode 100644 pmg/ssh_hardening.sls create mode 100644 pmg/update.sls create mode 100644 pmg/verify.sls create mode 100644 top.sls diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73ef9ab --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Python / virtualenv +.venv/ +venv/ +__pycache__/ +*.pyc + +# Salt runtime artifacts +.salt/ + +# IDE / OS files +.idea/ +.vscode/ +.DS_Store diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..2f7b8e5 --- /dev/null +++ b/.yamllint @@ -0,0 +1,16 @@ +extends: default + +ignore: | + .git/ + .venv/ + venv/ + +rules: + comments-indentation: disable + document-start: + present: false + line-length: + max: 120 + level: warning + truthy: + check-keys: false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..655b06f --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +SHELL := /usr/bin/env bash +.RECIPEPREFIX := > + +SALT_CALL ?= salt-call +SALT_LINT ?= salt-lint +YAMLLINT ?= yamllint +STATE_ROOT ?= . +PILLAR_ROOT ?= ./pillar +MINION_ID ?= p-mailgw-x2-01.lmru.tech + +.PHONY: lint check apply verify update-check update + +lint: +>@if command -v $(SALT_LINT) >/dev/null 2>&1; then \ +> $(SALT_LINT) pmg/*.sls pillar/*.sls top.sls; \ +>else \ +> echo "salt-lint не найден, пропускаю"; \ +>fi +>@if command -v $(YAMLLINT) >/dev/null 2>&1; then \ +> $(YAMLLINT) .; \ +>else \ +> echo "yamllint не найден, пропускаю"; \ +>fi + +check: +>sudo $(SALT_CALL) --local --id $(MINION_ID) --file-root=$(STATE_ROOT) --pillar-root=$(PILLAR_ROOT) state.apply pmg.init test=True + +apply: +>sudo $(SALT_CALL) --local --id $(MINION_ID) --file-root=$(STATE_ROOT) --pillar-root=$(PILLAR_ROOT) state.apply pmg.init + +verify: +>sudo $(SALT_CALL) --local --id $(MINION_ID) --file-root=$(STATE_ROOT) --pillar-root=$(PILLAR_ROOT) state.apply pmg.verify + +update-check: +>sudo $(SALT_CALL) --local --id $(MINION_ID) --file-root=$(STATE_ROOT) --pillar-root=$(PILLAR_ROOT) state.apply pmg.update test=True + +update: +>sudo $(SALT_CALL) --local --id $(MINION_ID) --file-root=$(STATE_ROOT) --pillar-root=$(PILLAR_ROOT) state.apply pmg.update diff --git a/README.md b/README.md index 6fd7653..ce780cc 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# lmru--devsecops--ansible +# Salt baseline для PMG + +Репозиторий содержит автономный набор Salt states для базовой эксплуатации и hardening Proxmox Mail Gateway. + +## Что внутри + +- `top.sls` — привязка состояний к minion ID. +- `pillar/pmg.sls` — переменные и параметры PMG. +- `pmg/*.sls` — состояния: + - `common` (базовые пакеты), + - `ssh_hardening`, + - `custom_check` (кастомная AV-проверка PMG через KESL), + - `security_agents` (Wazuh/Qualys), + - `update` (пакетные обновления + pre/post проверки PMG), + - `auto_updates` (security-only, без auto reboot), + - `audit_logging`, + - `pmg_baseline` (проверка/поддержание сервисов PMG), + - `verify` (проверки состояния). +- `pmg/files/*` — шаблоны конфигов. + +## Требования + +- Salt (рекомендуется актуальная LTS-ветка). +- Debian/PMG узлы с systemd. +- root-доступ на узел. + +## Быстрый запуск + +```bash +make lint +make check +make apply +make verify +make update-check +make update +``` + +По умолчанию `MINION_ID=p-mailgw-x2-01.lmru.tech`. Можно переопределить: + +```bash +make check MINION_ID=p-mailgw-x1-01.lmru.tech +``` + +## Основные переменные + +Все параметры задаются в `pillar/pmg.sls`: + +- `pmg.base_packages` +- `pmg.ssh_allow_users` +- `pmg.ssh_password_authentication` +- `pmg.journald_system_max_use` +- `pmg.journald_max_file_sec` +- `pmg.pmg_services` +- `pmg.update_dist_upgrade` +- `pmg.update_auto_reboot` +- `pmg.update_reboot_delay_minutes` +- `pmg.custom_check.*` +- `pmg.manage_hosts_file` +- `pmg.hosts_file_entries` +- `pmg.wazuh.*` +- `pmg.qualys.*` + +## Отдельное обновление PMG + +Перед обновлением: + +```bash +make update-check MINION_ID=p-mailgw-x1-01.lmru.tech +``` + +Применение: + +```bash +make update MINION_ID=p-mailgw-x1-01.lmru.tech +make verify MINION_ID=p-mailgw-x1-01.lmru.tech +``` + +State `pmg.update` выполняет: +- precheck кластера (`pmgcm status`); +- precheck критичных PMG сервисов; +- пакетное обновление (`pkg.uptodate`, с `dist_upgrade` по `pmg.update_dist_upgrade=true`); +- postcheck кластера и сервисов; +- уведомление, если требуется reboot (`/var/run/reboot-required`). +- опциональный автоперезапуск по `pmg.update_auto_reboot=true` (через `shutdown -r +N`). + +## PMG custom check (Kaspersky) + +Коротко про `pmg.custom_check`: +- готовит окружение (`pwgen`, `/var/log/mail_av`, `/usr/local/bin/pmg-custom-check`); +- готовит Kaspersky-учетку (`pmg.custom_check.ksc_superuser.*`; сейчас `superuser` + `NOPASSWD`); +- если `kesl/klnagent64` установлены — включает сервисы и `custom_check` в PMG, если нет — выводит уведомление "ставится через Kaspersky Administration Center"; сервисы включатся вторым прогоном после установки пакетов. diff --git a/pillar/pmg.sls b/pillar/pmg.sls new file mode 100644 index 0000000..076bdb7 --- /dev/null +++ b/pillar/pmg.sls @@ -0,0 +1,63 @@ +pmg: + base_packages: + - curl + - vim-tiny + - rsync + - ca-certificates + - jq + + ssh_allow_users: [] + ssh_password_authentication: true + + journald_system_max_use: 1G + journald_max_file_sec: 1month + + pmg_services: + - postfix + - pmg-smtp-filter + - pmgpolicy + - pmgdaemon + - pmgmirror + - pmgtunnel + update_dist_upgrade: true + update_auto_reboot: false + update_reboot_delay_minutes: 1 + + custom_check: + enabled: true + script_path: /usr/local/bin/pmg-custom-check + log_dir: /var/log/mail_av + managed_packages: + - pwgen + ksc_superuser: + enabled: true + name: superuser + manage_sudoers: true + required_services: + - kesl + - klnagent64 + + manage_hosts_file: false + hosts_file_entries: + - ip: 10.80.38.152 + hostname: p-mailgw-x1-01 + aliases: + - p-mailgw-x1-01.lmru.tech + - ip: 10.220.41.152 + hostname: p-mailgw-x2-01 + aliases: + - p-mailgw-x2-01.lmru.tech + + wazuh: + enabled: true + version: 4.14.0-1 + manager: 10.80.120.205 + group: test_auditd_unix + + qualys: + enabled: true + package_url: https://storage.yandexcloud.net/qualys-agent/QualysCloudAgent.deb + package_path: /opt/QualysCloudAgent.deb + activation_id: 5eb9ad8e-02cc-431a-9afb-b8caad175ec1 + customer_id: 4d868109-dad3-5536-82b4-9f3a6f3010bc + server_uri: https://qagpublic.qg1.apps.qualys.eu/CloudAgent/ diff --git a/pillar/top.sls b/pillar/top.sls new file mode 100644 index 0000000..a004122 --- /dev/null +++ b/pillar/top.sls @@ -0,0 +1,3 @@ +base: + '*': + - pmg diff --git a/pmg/audit_logging.sls b/pmg/audit_logging.sls new file mode 100644 index 0000000..0b41c37 --- /dev/null +++ b/pmg/audit_logging.sls @@ -0,0 +1,56 @@ +{% set journald_system_max_use = salt['pillar.get']('pmg:journald_system_max_use', '1G') %} +{% set journald_max_file_sec = salt['pillar.get']('pmg:journald_max_file_sec', '1month') %} + +pmg-journald-dropin-dir: + file.directory: + - name: /etc/systemd/journald.conf.d + - user: root + - group: root + - mode: '0755' + +pmg-journald-retention: + file.managed: + - name: /etc/systemd/journald.conf.d/99-retention.conf + - source: salt://pmg/files/journald-retention.conf.jinja + - template: jinja + - context: + journald_system_max_use: {{ journald_system_max_use }} + journald_max_file_sec: {{ journald_max_file_sec }} + - user: root + - group: root + - mode: '0644' + +pmg-restart-journald: + cmd.run: + - name: systemctl restart systemd-journald + - onchanges: + - file: pmg-journald-retention + +pmg-auditd-package: + pkg.installed: + - name: auditd + +pmg-audit-rules: + file.managed: + - name: /etc/audit/rules.d/99-pmg-baseline.rules + - source: salt://pmg/files/99-pmg-baseline.rules.jinja + - template: jinja + - user: root + - group: root + - mode: '0640' + +pmg-reload-audit-rules: + cmd.run: + - name: augenrules --load + - onchanges: + - file: pmg-audit-rules + +pmg-auditd-service: + service.running: + - name: auditd + - enable: true + +pmg-rsyslog-service: + service.running: + - name: rsyslog + - enable: true diff --git a/pmg/auto_updates.sls b/pmg/auto_updates.sls new file mode 100644 index 0000000..996b93f --- /dev/null +++ b/pmg/auto_updates.sls @@ -0,0 +1,28 @@ +pmg-updates-packages: + pkg.installed: + - pkgs: + - unattended-upgrades + - apt-listchanges + +pmg-apt-auto-upgrades: + file.managed: + - name: /etc/apt/apt.conf.d/20auto-upgrades + - source: salt://pmg/files/20auto-upgrades.jinja + - template: jinja + - user: root + - group: root + - mode: '0644' + +pmg-unattended-policy: + file.managed: + - name: /etc/apt/apt.conf.d/50unattended-upgrades + - source: salt://pmg/files/50unattended-upgrades.jinja + - template: jinja + - user: root + - group: root + - mode: '0644' + +pmg-unattended-service: + service.running: + - name: unattended-upgrades + - enable: true diff --git a/pmg/common.sls b/pmg/common.sls new file mode 100644 index 0000000..2d7c0fc --- /dev/null +++ b/pmg/common.sls @@ -0,0 +1,5 @@ +{% set base_packages = salt['pillar.get']('pmg:base_packages', []) %} + +pmg-base-packages: + pkg.installed: + - pkgs: {{ base_packages }} diff --git a/pmg/custom_check.sls b/pmg/custom_check.sls new file mode 100644 index 0000000..9023319 --- /dev/null +++ b/pmg/custom_check.sls @@ -0,0 +1,111 @@ +{% set custom_check_enabled = salt['pillar.get']('pmg:custom_check:enabled', True) %} +{% set custom_check_script_path = salt['pillar.get']('pmg:custom_check:script_path', '/usr/local/bin/pmg-custom-check') %} +{% set custom_check_log_dir = salt['pillar.get']('pmg:custom_check:log_dir', '/var/log/mail_av') %} +{% set custom_check_managed_packages = salt['pillar.get']('pmg:custom_check:managed_packages', ['pwgen']) %} +{% set custom_check_required_services = salt['pillar.get']('pmg:custom_check:required_services', ['kesl', 'klnagent64']) %} +{% set ksc_superuser_enabled = salt['pillar.get']('pmg:custom_check:ksc_superuser:enabled', False) %} +{% set ksc_superuser_name = salt['pillar.get']('pmg:custom_check:ksc_superuser:name', '') %} +{% set ksc_superuser_manage_sudoers = salt['pillar.get']('pmg:custom_check:ksc_superuser:manage_sudoers', True) %} +{% set kesl_installed = salt['pkg.version']('kesl') != '' %} +{% set klnagent64_installed = salt['pkg.version']('klnagent64') != '' %} +{% set custom_check_kaspersky_ready = kesl_installed and klnagent64_installed %} + +{% if custom_check_enabled %} +{% if ksc_superuser_enabled and ksc_superuser_name %} +pmg-custom-check-ksc-superuser: + user.present: + - name: {{ ksc_superuser_name }} + - shell: /bin/bash + - createhome: true + +{% if ksc_superuser_manage_sudoers %} +pmg-custom-check-ksc-superuser-sudo-package: + pkg.installed: + - name: sudo + +pmg-custom-check-ksc-superuser-sudoers: + file.managed: + - name: /etc/sudoers.d/90-kaspersky-superuser + - user: root + - group: root + - mode: '0440' + - contents: + - {{ ksc_superuser_name }} ALL = (ALL) NOPASSWD: ALL + - check_cmd: visudo -cf + - require: + - pkg: pmg-custom-check-ksc-superuser-sudo-package + - user: pmg-custom-check-ksc-superuser +{% endif %} +{% elif ksc_superuser_enabled %} +pmg-custom-check-ksc-superuser-missing-name: + test.fail_without_changes: + - name: "pmg.custom_check.ksc_superuser.enabled=true but pmg.custom_check.ksc_superuser.name is empty." +{% endif %} + +pmg-custom-check-managed-packages: + pkg.installed: + - pkgs: {{ custom_check_managed_packages }} + +pmg-custom-check-log-dir: + file.directory: + - name: {{ custom_check_log_dir }} + - user: root + - group: root + - mode: '0755' + +pmg-custom-check-log-file: + file.touch: + - name: {{ custom_check_log_dir }}/checked + - require: + - file: pmg-custom-check-log-dir + +pmg-custom-check-script: + file.managed: + - name: {{ custom_check_script_path }} + - source: salt://pmg/files/pmg-custom-check.jinja + - template: jinja + - user: root + - group: root + - mode: '0755' + - context: + custom_check_log_dir: {{ custom_check_log_dir }} + - require: + - pkg: pmg-custom-check-managed-packages + - file: pmg-custom-check-log-dir + +{% if custom_check_kaspersky_ready %} +pmg-custom-check-kaspersky-ready: + test.nop: + - name: "KESL and klnagent64 are installed." +{% else %} +pmg-custom-check-kaspersky-missing-note: + test.show_notification: + - text: "KESL/klnagent64 не найдены. Установите через Kaspersky Administration Center (сервисный superuser/NOPASSWD), затем повторите state.apply pmg.custom_check." +{% endif %} + +{% if custom_check_kaspersky_ready %} +{% for service_name in custom_check_required_services %} +pmg-custom-check-service-{{ service_name }}: + service.running: + - name: {{ service_name }} + - enable: true + - require: + - test: pmg-custom-check-kaspersky-ready +{% endfor %} + +pmg-custom-check-enable-in-pmg: + cmd.run: + - name: pmgsh set /config/admin --custom_check 1 + - unless: "pmgsh get /config/admin | grep -q '\"custom_check\" : 1'" + - require: + - file: pmg-custom-check-script + - pkg: pmg-custom-check-managed-packages +{% for service_name in custom_check_required_services %} + - service: pmg-custom-check-service-{{ service_name }} +{% endfor %} +{% endif %} +{% else %} +pmg-custom-check-disabled-note: + test.show_notification: + - text: "PMG custom_check disabled by pillar (pmg.custom_check.enabled=false)." +{% endif %} diff --git a/pmg/files/20auto-upgrades.jinja b/pmg/files/20auto-upgrades.jinja new file mode 100644 index 0000000..29a82d7 --- /dev/null +++ b/pmg/files/20auto-upgrades.jinja @@ -0,0 +1,4 @@ +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::Unattended-Upgrade "1"; +APT::Periodic::AutocleanInterval "7"; diff --git a/pmg/files/50unattended-upgrades.jinja b/pmg/files/50unattended-upgrades.jinja new file mode 100644 index 0000000..58b48df --- /dev/null +++ b/pmg/files/50unattended-upgrades.jinja @@ -0,0 +1,14 @@ +Unattended-Upgrade::Origins-Pattern { + "origin=Debian,codename=${distro_codename},label=Debian-Security"; + "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"; +}; + +Unattended-Upgrade::Package-Blacklist { +}; + +Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::InstallOnShutdown "false"; +Unattended-Upgrade::MailOnlyOnError "true"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "false"; +Unattended-Upgrade::SyslogEnable "true"; diff --git a/pmg/files/99-hardening.conf.jinja b/pmg/files/99-hardening.conf.jinja new file mode 100644 index 0000000..ea9cf66 --- /dev/null +++ b/pmg/files/99-hardening.conf.jinja @@ -0,0 +1,10 @@ +# Managed by Salt. +PasswordAuthentication {{ "yes" if ssh_password_authentication else "no" }} +PermitRootLogin prohibit-password +PubkeyAuthentication yes +X11Forwarding no +MaxAuthTries 3 +LoginGraceTime 30 +{% if ssh_allow_users %} +AllowUsers {{ " ".join(ssh_allow_users) }} +{% endif %} diff --git a/pmg/files/99-pmg-baseline.rules.jinja b/pmg/files/99-pmg-baseline.rules.jinja new file mode 100644 index 0000000..30b8782 --- /dev/null +++ b/pmg/files/99-pmg-baseline.rules.jinja @@ -0,0 +1,14 @@ +## Managed by Salt. +-D +-b 8192 + +-w /etc/passwd -p wa -k identity +-w /etc/group -p wa -k identity +-w /etc/shadow -p wa -k identity +-w /etc/gshadow -p wa -k identity + +-w /etc/sudoers -p wa -k privilege-escalation +-w /etc/sudoers.d/ -p wa -k privilege-escalation + +-w /etc/ssh/sshd_config -p wa -k ssh +-w /etc/ssh/sshd_config.d/ -p wa -k ssh diff --git a/pmg/files/journald-retention.conf.jinja b/pmg/files/journald-retention.conf.jinja new file mode 100644 index 0000000..2e338aa --- /dev/null +++ b/pmg/files/journald-retention.conf.jinja @@ -0,0 +1,3 @@ +[Journal] +SystemMaxUse={{ journald_system_max_use }} +MaxFileSec={{ journald_max_file_sec }} diff --git a/pmg/files/pmg-custom-check.jinja b/pmg/files/pmg-custom-check.jinja new file mode 100644 index 0000000..b50d6d6 --- /dev/null +++ b/pmg/files/pmg-custom-check.jinja @@ -0,0 +1,49 @@ +#!/bin/bash +# Managed by Salt: PMG custom check via Kaspersky KESL. + +set -u + +LOG_DIR="{{ custom_check_log_dir }}" +KESL_BIN="/opt/kaspersky/kesl/bin/kesl-control" +SCAN_TARGET="${2:-}" + +if [ -z "${SCAN_TARGET}" ]; then + echo "v1" + echo "ERROR: empty scan target" + exit 0 +fi + +mkdir -p "${LOG_DIR}" + +POLICY_NAME="$(pwgen 10 -1 -s -0)" +POLICY_FILE="${LOG_DIR}/${POLICY_NAME}.pol" + +cat > "${POLICY_FILE}" </dev/null +VIRUS_NAME="$("${KESL_BIN}" --start-task "${POLICY_NAME}" -W | grep 'DetectName' | awk -F'=' '{print $2}' | head -1)" +"${KESL_BIN}" --delete-task "${POLICY_NAME}" + +if [ -z "${VIRUS_NAME}" ]; then + rm -f "${POLICY_FILE}" + echo "Mail ${SCAN_TARGET} was checked successfully" >> "${LOG_DIR}/checked" + echo "v1" + echo "OK" +else + echo "In mail ${SCAN_TARGET} find ${VIRUS_NAME}" >> "${POLICY_FILE}" + echo "Mail ${SCAN_TARGET} was blocked. More info in ${POLICY_FILE}" >> "${LOG_DIR}/checked" + echo "v1" + echo "VIRUS: ${VIRUS_NAME}" +fi + +exit 0 diff --git a/pmg/init.sls b/pmg/init.sls new file mode 100644 index 0000000..da98408 --- /dev/null +++ b/pmg/init.sls @@ -0,0 +1,8 @@ +include: + - pmg.common + - pmg.ssh_hardening + - pmg.custom_check + - pmg.security_agents + - pmg.auto_updates + - pmg.audit_logging + - pmg.pmg_baseline diff --git a/pmg/pmg_baseline.sls b/pmg/pmg_baseline.sls new file mode 100644 index 0000000..8c90ad1 --- /dev/null +++ b/pmg/pmg_baseline.sls @@ -0,0 +1,31 @@ +{% set pmg_services = salt['pillar.get']('pmg:pmg_services', []) %} +{% set manage_hosts_file = salt['pillar.get']('pmg:manage_hosts_file', False) %} +{% set hosts_file_entries = salt['pillar.get']('pmg:hosts_file_entries', []) %} + +{% for svc in pmg_services %} +pmg-service-{{ svc }}: + service.running: + - name: {{ svc }} + - enable: true +{% endfor %} + +pmg-cluster-status: + cmd.run: + - name: pmgcm status + +{% if manage_hosts_file %} +pmg-managed-hosts: + file.blockreplace: + - name: /etc/hosts + - marker_start: "#-- SALT PMG HOSTS START --" + - marker_end: "#-- SALT PMG HOSTS END --" + - append_if_not_found: true + - content: | +{% for host in hosts_file_entries %} +{% if host.get('aliases', []) %} + {{ host['ip'] }} {{ host['hostname'] }} {{ ' '.join(host['aliases']) }} +{% else %} + {{ host['ip'] }} {{ host['hostname'] }} +{% endif %} +{% endfor %} +{% endif %} diff --git a/pmg/security_agents.sls b/pmg/security_agents.sls new file mode 100644 index 0000000..15e4409 --- /dev/null +++ b/pmg/security_agents.sls @@ -0,0 +1,69 @@ +{% set wazuh_enabled = salt['pillar.get']('pmg:wazuh:enabled', True) %} +{% set wazuh_version = salt['pillar.get']('pmg:wazuh:version', '4.14.0-1') %} +{% set wazuh_manager = salt['pillar.get']('pmg:wazuh:manager', '10.80.120.205') %} +{% set wazuh_group = salt['pillar.get']('pmg:wazuh:group', 'test_auditd_unix') %} +{% set wazuh_pkg_name = 'wazuh-agent_' ~ wazuh_version ~ '_amd64.deb' %} +{% set wazuh_pkg_url = 'https://packages.wazuh.com/4.x/apt/pool/main/w/wazuh-agent/' ~ wazuh_pkg_name %} +{% set wazuh_pkg_path = '/var/tmp/' ~ wazuh_pkg_name %} + +{% set qualys_enabled = salt['pillar.get']('pmg:qualys:enabled', True) %} +{% set qualys_package_url = salt['pillar.get']('pmg:qualys:package_url', 'https://storage.yandexcloud.net/qualys-agent/QualysCloudAgent.deb') %} +{% set qualys_package_path = salt['pillar.get']('pmg:qualys:package_path', '/opt/QualysCloudAgent.deb') %} +{% set qualys_activation_id = salt['pillar.get']('pmg:qualys:activation_id', '') %} +{% set qualys_customer_id = salt['pillar.get']('pmg:qualys:customer_id', '') %} +{% set qualys_server_uri = salt['pillar.get']('pmg:qualys:server_uri', 'https://qagpublic.qg1.apps.qualys.eu/CloudAgent/') %} + +{% if wazuh_enabled %} +pmg-wazuh-download: + cmd.run: + - name: wget -qO {{ wazuh_pkg_path }} {{ wazuh_pkg_url }} + - unless: test -f {{ wazuh_pkg_path }} + +pmg-wazuh-install: + cmd.run: + - name: WAZUH_MANAGER='{{ wazuh_manager }}' WAZUH_AGENT_GROUP='{{ wazuh_group }}' WAZUH_AGENT_NAME="$(hostname -f)" dpkg -i {{ wazuh_pkg_path }} + - unless: dpkg-query -W -f='${Status}' wazuh-agent 2>/dev/null | grep -q 'install ok installed' + - require: + - cmd: pmg-wazuh-download + +pmg-wazuh-service: + service.running: + - name: wazuh-agent + - enable: true + - require: + - cmd: pmg-wazuh-install +{% endif %} + +{% if qualys_enabled and qualys_package_path and qualys_activation_id and qualys_customer_id %} +pmg-qualys-download: + cmd.run: + - name: wget -qO {{ qualys_package_path }} {{ qualys_package_url }} + - unless: test -f {{ qualys_package_path }} + +pmg-qualys-install: + cmd.run: + - name: dpkg --install {{ qualys_package_path }} + - onlyif: test -f {{ qualys_package_path }} + - unless: dpkg-query -W -f='${Status}' qualys-cloud-agent 2>/dev/null | grep -q 'install ok installed' + - require: + - cmd: pmg-qualys-download + +pmg-qualys-activate: + cmd.run: + - name: /usr/local/qualys/cloud-agent/bin/qualys-cloud-agent.sh ActivationId={{ qualys_activation_id }} CustomerId={{ qualys_customer_id }} ServerUri={{ qualys_server_uri }} + - onchanges: + - cmd: pmg-qualys-install + - onlyif: test -x /usr/local/qualys/cloud-agent/bin/qualys-cloud-agent.sh + +pmg-qualys-service: + service.running: + - name: qualys-cloud-agent + - enable: true + - onlyif: test -f {{ qualys_package_path }} + - require: + - cmd: pmg-qualys-install +{% elif qualys_enabled %} +pmg-qualys-skipped: + test.show_notification: + - text: "Qualys agent skipped: set package_url + package_path + activation_id + customer_id in pillar." +{% endif %} diff --git a/pmg/ssh_hardening.sls b/pmg/ssh_hardening.sls new file mode 100644 index 0000000..aa8fe8b --- /dev/null +++ b/pmg/ssh_hardening.sls @@ -0,0 +1,35 @@ +{% set ssh_allow_users = salt['pillar.get']('pmg:ssh_allow_users', []) %} +{% set ssh_password_authentication = salt['pillar.get']('pmg:ssh_password_authentication', True) %} + +pmg-sshd-dropin-dir: + file.directory: + - name: /etc/ssh/sshd_config.d + - user: root + - group: root + - mode: '0755' + +pmg-sshd-hardening-config: + file.managed: + - name: /etc/ssh/sshd_config.d/99-hardening.conf + - source: salt://pmg/files/99-hardening.conf.jinja + - template: jinja + - context: + ssh_allow_users: {{ ssh_allow_users }} + ssh_password_authentication: {{ ssh_password_authentication }} + - user: root + - group: root + - mode: '0644' + +pmg-validate-sshd-config: + cmd.run: + - name: sshd -t + - onchanges: + - file: pmg-sshd-hardening-config + +pmg-sshd-service: + service.running: + - name: ssh + - enable: true + - reload: true + - watch: + - cmd: pmg-validate-sshd-config diff --git a/pmg/update.sls b/pmg/update.sls new file mode 100644 index 0000000..933388f --- /dev/null +++ b/pmg/update.sls @@ -0,0 +1,50 @@ +{% set pmg_services = salt['pillar.get']('pmg:pmg_services', []) %} +{% set pmg_update_dist_upgrade = salt['pillar.get']('pmg:update_dist_upgrade', True) %} +{% set pmg_update_auto_reboot = salt['pillar.get']('pmg:update_auto_reboot', False) %} +{% set pmg_update_reboot_delay_minutes = salt['pillar.get']('pmg:update_reboot_delay_minutes', 1) %} + +pmg-update-precheck-cluster: + cmd.run: + - name: pmgcm status + +{% for svc in pmg_services %} +pmg-update-precheck-service-{{ svc }}: + cmd.run: + - name: systemctl is-active --quiet {{ svc }} +{% endfor %} + +pmg-update-packages: + pkg.uptodate: + - refresh: true + - dist_upgrade: {{ pmg_update_dist_upgrade }} + +pmg-update-postcheck-cluster: + cmd.run: + - name: pmgcm status + - require: + - pkg: pmg-update-packages + +{% for svc in pmg_services %} +pmg-update-postcheck-service-{{ svc }}: + cmd.run: + - name: systemctl is-active --quiet {{ svc }} + - require: + - pkg: pmg-update-packages +{% endfor %} + +pmg-update-reboot-required-note: + test.show_notification: + - text: "После обновления требуется reboot: /var/run/reboot-required" + - onlyif: test -f /var/run/reboot-required + +{% if pmg_update_auto_reboot %} +pmg-update-schedule-reboot: + cmd.run: + - name: "shutdown -r +{{ pmg_update_reboot_delay_minutes }} \"Salt pmg.update: reboot required\"" + - onlyif: test -f /var/run/reboot-required + - require: + - cmd: pmg-update-postcheck-cluster +{% for svc in pmg_services %} + - cmd: pmg-update-postcheck-service-{{ svc }} +{% endfor %} +{% endif %} diff --git a/pmg/verify.sls b/pmg/verify.sls new file mode 100644 index 0000000..ebb9595 --- /dev/null +++ b/pmg/verify.sls @@ -0,0 +1,15 @@ +{% set pmg_services = salt['pillar.get']('pmg:pmg_services', []) %} + +{% for svc in pmg_services %} +pmg-verify-service-{{ svc }}: + cmd.run: + - name: systemctl is-active --quiet {{ svc }} +{% endfor %} + +pmg-verify-cluster: + cmd.run: + - name: pmgcm status + +pmg-verify-sshd-config: + cmd.run: + - name: sshd -t diff --git a/top.sls b/top.sls new file mode 100644 index 0000000..fb0d4d6 --- /dev/null +++ b/top.sls @@ -0,0 +1,3 @@ +base: + 'p-mailgw-x*-0*': + - pmg.init