← Все заметки
Мониторинг и безопасность

KSC Open API: проверка TLS-цепочки и безопасная доставка JSON в Inventory API

Разбор ошибки self-signed certificate in certificate chain, HTTPS через SSH-туннель и ограниченная SSH-учётная запись для загрузки RAM-отчётов в FastAPI/SQLite.

Статус: актуальноОпубликовано: 2026-10-22Обновлено: 2026-10-23

Проверено на: Kaspersky Security Center 15.1 · Python 3.12 · OpenSSL · AlmaLinux 9 · FastAPI · OpenSSH

Задача: зашифровать недостаточно — нужно проверить сервер

Рабочий пилот инвентаризации оборудования через KSC Open API первоначально использовал verify=False при подключении через локальный SSH-туннель. Для постоянного решения такой режим неприемлем: клиент не проверяет, что отвечает именно доверенный сервер.

Нужно было включить полноценную проверку TLS без ручной замены сертификатов KSC, которая могла бы затронуть доверие между Administration Server и Network Agent. Отдельно требовалось доставить JSON в Inventory API без копирования API-токена на управляющую машину и без автоматизированного доступа по SSH как root.

HTTPS через SSH-туннель и SAN сертификата

Open API в пилоте был доступен через локальный TCP-туннель. Сертификат предъявлялся для условного имени ksc.example.test, а не для 127.0.0.1. При подключении по IP проверка имени не пройдёт.

Для проверки без изменения DNS можно использовать curl --resolve:

curl --noproxy '*' \
  --cacert ./trusted-ksc-ca.pem \
  --resolve 'ksc.example.test:13299:127.0.0.1' \
  -o /dev/null -sS \
  -w 'HTTP=%{http_code} TLS=%{ssl_verify_result}\n' \
  'https://ksc.example.test:13299/'

Критерий TLS — ssl_verify_result=0. Код 401 при обращении без учётных данных не означает проблему с сертификатом.

Если SDK должен использовать DNS-имя из сертификата при локальном туннеле, на управляющем компьютере можно временно настроить соответствующее разрешение имени в 127.0.0.1. Это не требует менять корпоративный DNS, но влияет на все локальные обращения к данному имени; при этом туннель должен быть активен.

Почему curl проходил, а Python requests — нет

При диагностике KSC присылал два сертификата. Первый серверный имел тот же Subject, что и издатель, но его Authority Key Identifier совпадал с Subject Key Identifier второго, а не первого сертификата. Поэтому сам первый сертификат не являлся корректным доверенным корнем для Python.

Проверка только с первым сертификатом давала:

unable to get local issuer certificate
CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain

Важно проверять всю цепочку:

openssl s_client -connect 127.0.0.1:13299 \
  -servername ksc.example.test -showcerts </dev/null

При диагностике отпечаток первого сертификата сравнили с сертификатом на самом KSC по независимому административному каналу. Только после этого разрешили доверие к издателю. Не следует делать «trust on first use» на основании одного непроверенного HTTPS-соединения.

В пилоте оказалось достаточно сохранить второй сертификат как trusted-ksc-ca.pem и выполнить:

openssl verify \
  -CAfile trusted-ksc-ca.pem \
  -verify_hostname ksc.example.test \
  ksc-leaf.pem

Результат OK подтвердился также через requests с verify="trusted-ksc-ca.pem". Никакой verify=False или отключения проверки имени не потребовалось.

Подключение KlAkOAPI с проверкой цепочки

Пример конфигурации (условные пути/имена; библиотека SDK приобретается и используется в соответствии с условиями Kaspersky):

from KlAkOAPI.AdmServer import KlAkAdmServer

url = "https://ksc.example.test:13299"
ca_file = "/opt/inventory/certs/trusted-ksc-ca.pem"

server = KlAkAdmServer.Create(
    url,
    user_account,
    password,
    internal_user=False,
    verify=ca_file,
)

# После получения короткоживущего токена gateway:
agent = KlAkAdmServer.CreateGateway(url, gateway_token, ca_file)

Для KlAkAdmServer.CreateGateway также требуется параметр верификации сертификата. Используемые SDK-методы проверили отдельным тестом NagRdu.GetCurrentHostState(): соединение установлено, HTTP 200 и отсутствие InsecureRequestWarning.

Важное эксплуатационное ограничение: при штатной ротации сертификатов KSC необходимо заново подтвердить цепочку доверия клиента. Изменение сертификатов самого KSC не выполняли. Следите за датами истечения действующего и резервного сертификата.

Inventory API: принцип локального доверия

Сервер приёма отчётов использовал HTTPS FastAPI за Nginx, доступный только через 127.0.0.1:443. TLS выдавался корпоративным CA. Запросы защищены заголовком X-Inventory-Key; значение токена хранится в root-only конфигурации, доступной только локальному приёмнику.

Python / KSC (управляющая машина)
  -> ZIP с JSON
  -> отдельный SSH-ключ
  -> inventory-ingest@inventory.example.test
  -> forced command
  -> root-owned import_ram_stdin.py
  -> HTTPS 127.0.0.1 + SNI + проверка CA
  -> FastAPI
  -> SQLite

Доступ на принимающий сервер не требует открывать внешний HTTPS-порт. Отчёт поступает по stdin через SSH; код проверяет JSON и отправляет его в локальный API.

SSH: не root-сессия, а единственная команда

Для автоматизации создан отдельный непривилегированный пользователь inventory-ingest. Его публичный SSH-ключ ограничен в authorized_keys:

restrict,command="/usr/bin/sudo -n /usr/local/sbin/inventory-import-ram-stdin" ssh-ed25519 AAAA... example-inventory-ingest

Это пример, а не готовый ключ. restrict блокирует форвардинг/PTY, а command= ограничивает действия, но полноценная защита требует также безопасных прав файлов и узкого sudoers:

inventory-ingest ALL=(root) NOPASSWD: /usr/local/sbin/inventory-import-ram-stdin ""

В проверенном пилоте импортёр и его Python-модуль были root:root 700, конфигурация с API-токеном root:root 600, правило sudo 0440. Пользователь inventory-ingest не мог менять импортёр или непосредственно читать конфигурацию.

С управляющей машины отправка выполнялась так:

ssh -T -i ~/.ssh/inventory_ingest_ed25519 \
  -o BatchMode=yes -o IdentitiesOnly=yes \
  -o StrictHostKeyChecking=yes \
  inventory-ingest@inventory.example.test \
  < ram-PC-DEMO-01.json

Дополнительное усиление перед масштабированием: закрепить IP-источник подключения в правилах SSH/сети, защитить private key, включить аудит импорта, проверить отсутствие возможности обхода forced command через окружение и ограничить права сервиса/токена приёма.

Проверенный результат и следующие шаги

Один пилотный хост прошёл полный цикл без предупреждений TLS: загрузка коллектора через KSC, асинхронное выполнение на Network Agent, скачивание ZIP, JSON-проверка, SSH-доставка под inventory-ingest, HTTP 201 stored и запись в SQLite.

Это успешный пилот, не завершённое массовое внедрение. Перед расширением необходимы очередь, ограничение параллелизма, журналирование, резервное копирование SQLite, ревизия широких разрешений KSC и контроль ротации сертификатов.

Связанные статьи: аппаратная RAM-инвентаризация и ошибка 1184 и RBAC KSC.

Публичная реализация

В KSC Hardware Inventory v0.2.1 доступны проверяемое TLS-подключение к KSC и Gateway, валидация и SSH-импорт отчёта, обзор модели угроз и протокол проверки реального пилота. API-токены, закрытые ключи, доверенные сертификаты инфраструктуры и реальные результаты инвентаризации в публичный репозиторий не включены.

Документация