352 lines
13 KiB
YAML
352 lines
13 KiB
YAML
---
|
|
- name: Deploy DetMir read-only portal
|
|
hosts: proxmox
|
|
become: true
|
|
gather_facts: false
|
|
|
|
vars:
|
|
aw_repo_root: "{{ playbook_dir | dirname }}"
|
|
aw_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}"
|
|
detmir_portal_bind: "{{ detmir_portal_bind_override | default('127.0.0.1:8720') }}"
|
|
detmir_portal_env_path: "/etc/detmir-portal.env"
|
|
detmir_portal_worktime_url: "{{ detmir_portal_worktime_url_override | default(aw_worktime_report_base | default('http://192.0.2.13:5610', true), true) }}"
|
|
detmir_portal_one_c_host: "{{ hostvars[(groups['proxmox'] | default([]) | first) | default('192.0.2.2', true)].ansible_host | default((groups['proxmox'] | default([]) | first) | default('192.0.2.2', true), true) }}"
|
|
detmir_portal_one_c_url: "{{ detmir_portal_one_c_url_override | default('http://' + detmir_portal_one_c_host + ':8710', true) }}"
|
|
detmir_portal_workforce_policy_path: "/etc/detmir-portal-workforce-policy.json"
|
|
detmir_portal_ueba_policy_path: "/etc/detmir-portal-ueba-policy.yaml"
|
|
detmir_portal_readiness_bundle_dir: "{{ detmir_portal_readiness_bundle_dir_override | default('/var/lib/activitywatch/health/readiness-bundle', true) }}"
|
|
|
|
tasks:
|
|
- name: Check local detmir-portal binary
|
|
ansible.builtin.stat:
|
|
path: "{{ aw_rust_release_dir }}/detmir-portal"
|
|
delegate_to: localhost
|
|
become: false
|
|
register: detmir_portal_binary
|
|
|
|
- name: Fail when detmir-portal binary is absent
|
|
ansible.builtin.fail:
|
|
msg: "Missing {{ aw_rust_release_dir }}/detmir-portal. Build with cargo build --release -p detmir-portal."
|
|
when: not (detmir_portal_binary.stat.exists | default(false))
|
|
|
|
- name: Install detmir-portal binary
|
|
ansible.builtin.copy:
|
|
src: "{{ aw_rust_release_dir }}/detmir-portal"
|
|
dest: /usr/local/bin/detmir-portal
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Install detmir-portal environment
|
|
ansible.builtin.copy:
|
|
dest: "{{ detmir_portal_env_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
DETMIR_PORTAL_BIND={{ detmir_portal_bind }}
|
|
DETMIR_PORTAL_STATUS_CMD=detmir-status --json
|
|
DETMIR_PORTAL_CHECK_CMD='cat /var/lib/detmir-ai/latest-run/detmir-check.json'
|
|
DETMIR_PORTAL_FAILED_UNITS_CMD=systemctl --failed --no-pager
|
|
DETMIR_PORTAL_WORKTIME_URL={{ detmir_portal_worktime_url }}
|
|
DETMIR_PORTAL_ONE_C_URL={{ detmir_portal_one_c_url }}
|
|
DETMIR_PORTAL_WORKFORCE_POLICY_PATH={{ detmir_portal_workforce_policy_path }}
|
|
DETMIR_PORTAL_UEBA_POLICY_PATH={{ detmir_portal_ueba_policy_path }}
|
|
DETMIR_PORTAL_TIMEOUT_SECONDS=25
|
|
DETMIR_PORTAL_STATE_DIR=/var/lib/detmir-portal
|
|
DETMIR_PORTAL_DLP_DB_PATH=/var/lib/activitywatch/dlp_warehouse.sqlite
|
|
DETMIR_PORTAL_EVIDENCE_ROOT=/var/lib/detmir-portal/evidence
|
|
DETMIR_PORTAL_READINESS_BUNDLE_DIR={{ detmir_portal_readiness_bundle_dir }}
|
|
DETMIR_PORTAL_EVIDENCE_LIMIT=30
|
|
DETMIR_PORTAL_EVIDENCE_MAX_BYTES=8388608
|
|
SECURITY_EVENTS_BACKEND={{ detmir_security_events_backend | default('disabled') }}
|
|
CLICKHOUSE_URL={{ detmir_clickhouse_url | default('http://127.0.0.1:8123') }}
|
|
CLICKHOUSE_DATABASE={{ detmir_clickhouse_database | default('analytics_1c') }}
|
|
CLICKHOUSE_USER={{ detmir_clickhouse_user | default('default') }}
|
|
CLICKHOUSE_PASSWORD={{ detmir_clickhouse_password | default('') }}
|
|
|
|
- name: Preserve local ClickHouse security-events settings when available
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
python3 - <<'PY'
|
|
from pathlib import Path
|
|
|
|
source = Path("/opt/activitywatch/clickhouse-1c/.env")
|
|
target = Path("{{ detmir_portal_env_path }}")
|
|
if not source.exists() or not target.exists():
|
|
raise SystemExit(0)
|
|
|
|
kv = {}
|
|
for line in source.read_text().splitlines():
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
kv[key.strip()] = value.strip().strip('"').strip("'")
|
|
|
|
updates = {
|
|
"SECURITY_EVENTS_BACKEND": "clickhouse",
|
|
"CLICKHOUSE_URL": "http://127.0.0.1:8123",
|
|
"CLICKHOUSE_DATABASE": kv.get("CLICKHOUSE_DB", "analytics_1c"),
|
|
"CLICKHOUSE_USER": kv.get("CLICKHOUSE_USER", "default"),
|
|
"CLICKHOUSE_PASSWORD": kv.get("CLICKHOUSE_PASSWORD", ""),
|
|
}
|
|
|
|
lines = []
|
|
seen = set()
|
|
for line in target.read_text().splitlines():
|
|
if "=" in line and not line.startswith("#"):
|
|
key = line.split("=", 1)[0].strip()
|
|
if key in updates:
|
|
lines.append(f"{key}={updates[key]}")
|
|
seen.add(key)
|
|
continue
|
|
lines.append(line)
|
|
for key, value in updates.items():
|
|
if key not in seen:
|
|
lines.append(f"{key}={value}")
|
|
target.write_text("\n".join(lines) + "\n")
|
|
PY
|
|
args:
|
|
executable: /bin/bash
|
|
no_log: true
|
|
when: detmir_security_events_backend | default('clickhouse') == 'clickhouse'
|
|
|
|
- name: Remove stale detmir-portal systemd overrides
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: absent
|
|
loop:
|
|
- /etc/systemd/system/detmir-portal.service.d/20-timeouts.conf
|
|
- /etc/systemd/system/detmir-portal.service.d/30-warm-cache.conf
|
|
register: detmir_portal_stale_overrides
|
|
|
|
- name: Install initial workforce policy when absent
|
|
ansible.builtin.copy:
|
|
dest: "{{ detmir_portal_workforce_policy_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
force: false
|
|
content: "{{ lookup('file', aw_repo_root + '/configs/detmir-workforce-policy.example.json') }}"
|
|
|
|
- name: Install initial UEBA risk policy when absent
|
|
ansible.builtin.copy:
|
|
dest: "{{ detmir_portal_ueba_policy_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
force: false
|
|
content: "{{ lookup('file', aw_repo_root + '/configs/detmir-ueba-risk-policy.example.yaml') }}"
|
|
|
|
- name: Ensure detmir-portal state and evidence directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|
|
loop:
|
|
- /var/lib/detmir-portal
|
|
- /var/lib/detmir-portal/evidence
|
|
- /var/lib/detmir-portal/evidence/screenshots
|
|
|
|
- name: Install detmir-portal systemd service
|
|
ansible.builtin.copy:
|
|
dest: /etc/systemd/system/detmir-portal.service
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
[Unit]
|
|
Description=DetMir Operator Portal
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
EnvironmentFile=-{{ detmir_portal_env_path }}
|
|
EnvironmentFile=-/etc/detmir/detmir-check.env
|
|
ExecStart=/usr/local/bin/detmir-portal
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
register: detmir_portal_service_unit
|
|
|
|
- name: Reload systemd
|
|
ansible.builtin.systemd:
|
|
daemon_reload: true
|
|
|
|
- name: Enable and restart detmir-portal
|
|
ansible.builtin.systemd:
|
|
name: detmir-portal.service
|
|
enabled: true
|
|
state: restarted
|
|
|
|
- name: Verify detmir-portal local health
|
|
ansible.builtin.uri:
|
|
url: "http://{{ detmir_portal_bind }}/api/health"
|
|
method: GET
|
|
status_code: 200
|
|
return_content: true
|
|
register: detmir_portal_health
|
|
failed_when: detmir_portal_health.status != 200 or detmir_portal_health.json.sources is not defined
|
|
changed_when: false
|
|
|
|
- name: Verify detmir-portal reports API
|
|
ansible.builtin.uri:
|
|
url: "http://{{ detmir_portal_bind }}/api/reports"
|
|
method: GET
|
|
status_code: 200
|
|
return_content: true
|
|
register: detmir_portal_reports
|
|
failed_when: >
|
|
detmir_portal_reports.status != 200
|
|
or detmir_portal_reports.json.kpis is not defined
|
|
or (
|
|
'derived detections/cases' not in (detmir_portal_reports.json.markdown | default(''))
|
|
and 'расчетными выводами' not in (detmir_portal_reports.json.markdown | default(''))
|
|
)
|
|
changed_when: false
|
|
|
|
- name: Deploy DetMir DLP evidence API on AW server
|
|
hosts: aw_server
|
|
become: true
|
|
gather_facts: false
|
|
|
|
vars:
|
|
aw_repo_root: "{{ playbook_dir | dirname }}"
|
|
aw_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}"
|
|
detmir_evidence_bind_host: "{{ ansible_host | default(inventory_hostname, true) }}"
|
|
detmir_evidence_bind: "{{ detmir_evidence_bind_override | default(detmir_evidence_bind_host + ':8721', true) }}"
|
|
detmir_evidence_env_path: "/etc/detmir-portal-evidence.env"
|
|
detmir_evidence_upload_token_path: "/var/lib/activitywatch/dlp-evidence/upload-token"
|
|
|
|
tasks:
|
|
- name: Check local detmir-portal binary for evidence service
|
|
ansible.builtin.stat:
|
|
path: "{{ aw_rust_release_dir }}/detmir-portal"
|
|
delegate_to: localhost
|
|
become: false
|
|
register: detmir_evidence_binary
|
|
|
|
- name: Fail when detmir-portal evidence binary is absent
|
|
ansible.builtin.fail:
|
|
msg: "Missing {{ aw_rust_release_dir }}/detmir-portal. Build with cargo build --release -p detmir-portal."
|
|
when: not (detmir_evidence_binary.stat.exists | default(false))
|
|
|
|
- name: Install detmir-portal evidence binary
|
|
ansible.builtin.copy:
|
|
src: "{{ aw_rust_release_dir }}/detmir-portal"
|
|
dest: /usr/local/bin/detmir-portal-evidence
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Ensure detmir evidence directories on AW server
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|
|
loop:
|
|
- /var/lib/activitywatch/dlp-evidence
|
|
- /var/lib/activitywatch/dlp-evidence/screenshots
|
|
|
|
- name: Check detmir evidence upload token
|
|
ansible.builtin.stat:
|
|
path: "{{ detmir_evidence_upload_token_path }}"
|
|
register: detmir_evidence_upload_token_stat
|
|
no_log: true
|
|
|
|
- name: Generate detmir evidence upload token
|
|
ansible.builtin.shell: |
|
|
set -euo pipefail
|
|
umask 077
|
|
python3 - <<'PY' > '{{ detmir_evidence_upload_token_path }}'
|
|
import secrets
|
|
print(secrets.token_urlsafe(48))
|
|
PY
|
|
args:
|
|
executable: /bin/bash
|
|
when: not (detmir_evidence_upload_token_stat.stat.exists | default(false))
|
|
no_log: true
|
|
|
|
- name: Fix detmir evidence upload token permissions
|
|
ansible.builtin.file:
|
|
path: "{{ detmir_evidence_upload_token_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0600"
|
|
no_log: true
|
|
|
|
- name: Read detmir evidence upload token
|
|
ansible.builtin.slurp:
|
|
src: "{{ detmir_evidence_upload_token_path }}"
|
|
register: detmir_evidence_upload_token_slurp
|
|
no_log: true
|
|
|
|
- name: Install detmir evidence API environment
|
|
ansible.builtin.copy:
|
|
dest: "{{ detmir_evidence_env_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
DETMIR_PORTAL_BIND={{ detmir_evidence_bind }}
|
|
DETMIR_PORTAL_EVIDENCE_ONLY=true
|
|
DETMIR_PORTAL_STATE_DIR=/var/lib/activitywatch/dlp-evidence
|
|
DETMIR_PORTAL_DLP_DB_PATH=/var/lib/activitywatch/dlp_warehouse.sqlite
|
|
DETMIR_PORTAL_EVIDENCE_ROOT=/var/lib/activitywatch/dlp-evidence
|
|
DETMIR_PORTAL_READINESS_BUNDLE_DIR=/var/lib/activitywatch/health/readiness-bundle
|
|
DETMIR_PORTAL_EVIDENCE_LIMIT=30
|
|
DETMIR_PORTAL_EVIDENCE_MAX_BYTES=8388608
|
|
DETMIR_PORTAL_EVIDENCE_UPLOAD_TOKEN={{ detmir_evidence_upload_token_slurp.content | b64decode | trim }}
|
|
no_log: true
|
|
|
|
- name: Install detmir evidence API systemd service
|
|
ansible.builtin.copy:
|
|
dest: /etc/systemd/system/detmir-portal-evidence.service
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
content: |
|
|
[Unit]
|
|
Description=DetMir DLP Evidence API
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
EnvironmentFile=-{{ detmir_evidence_env_path }}
|
|
ExecStart=/usr/local/bin/detmir-portal-evidence
|
|
Restart=on-failure
|
|
RestartSec=5s
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
register: detmir_evidence_service_unit
|
|
|
|
- name: Reload systemd for evidence API
|
|
ansible.builtin.systemd:
|
|
daemon_reload: true
|
|
when: detmir_evidence_service_unit.changed
|
|
|
|
- name: Enable and restart detmir evidence API
|
|
ansible.builtin.systemd:
|
|
name: detmir-portal-evidence.service
|
|
enabled: true
|
|
state: restarted
|
|
|
|
- name: Verify detmir evidence API health
|
|
ansible.builtin.uri:
|
|
url: "http://{{ detmir_evidence_bind }}/api/health"
|
|
method: GET
|
|
status_code: 200
|
|
return_content: true
|
|
register: detmir_evidence_health
|
|
failed_when: detmir_evidence_health.status != 200 or detmir_evidence_health.json.mode | default('') != 'evidence-only'
|
|
changed_when: false
|