Compare commits
20 Commits
c2b9ea9dcc
...
1f52e74754
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f52e74754 | |||
| 080eda2482 | |||
| 70309d2b21 | |||
| 6028b36f14 | |||
| 46f5b022fe | |||
| 2eccd21884 | |||
| 71b02a5ec8 | |||
| 78c4c851e9 | |||
| 1ad8968f56 | |||
| 22eda53d79 | |||
| 3b597aad7d | |||
| be68b1a239 | |||
| 1b3ccc6f35 | |||
| 8d741e418f | |||
| f1acef4dab | |||
| de388b14ea | |||
| 5db8346292 | |||
| 3a86aa4d6a | |||
| a38ed7a5a7 | |||
| cd69326e89 |
+1
-2
@@ -5,8 +5,7 @@ Thumbs.db
|
|||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
.obsidian
|
.obsidian
|
||||||
.obsidian/*
|
.obsidian/
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
!".obsidian\\plugins\\obsidian-git"
|
!".obsidian\\plugins\\obsidian-git"
|
||||||
!".obsidian\\plugins\\obsidian-style-settings"
|
|
||||||
|
|||||||
Vendored
+3
-2
@@ -5,7 +5,7 @@
|
|||||||
"attachmentFolderPath": "99 System/Cache",
|
"attachmentFolderPath": "99 System/Cache",
|
||||||
"trashOption": "local",
|
"trashOption": "local",
|
||||||
"showUnsupportedFiles": true,
|
"showUnsupportedFiles": true,
|
||||||
"promptDelete": false,
|
"promptDelete": true,
|
||||||
"pdfExportSettings": {
|
"pdfExportSettings": {
|
||||||
"includeName": false,
|
"includeName": false,
|
||||||
"pageSize": "Legal",
|
"pageSize": "Legal",
|
||||||
@@ -22,5 +22,6 @@
|
|||||||
"userIgnoreFilters": [
|
"userIgnoreFilters": [
|
||||||
"99 System/"
|
"99 System/"
|
||||||
],
|
],
|
||||||
"showInlineTitle": true
|
"showInlineTitle": true,
|
||||||
|
"uriCallbacks": true
|
||||||
}
|
}
|
||||||
Vendored
+3
-3
@@ -5,12 +5,12 @@
|
|||||||
"math-blocks"
|
"math-blocks"
|
||||||
],
|
],
|
||||||
"theme": "obsidian",
|
"theme": "obsidian",
|
||||||
"interfaceFontFamily": "Fira Sans,FiraCode Nerd Font",
|
"interfaceFontFamily": "Fira Sans",
|
||||||
"textFontFamily": "Fira Sans,FiraCode Nerd Font",
|
"textFontFamily": "Fira Sans",
|
||||||
"baseFontSizeAction": true,
|
"baseFontSizeAction": true,
|
||||||
"baseFontSize": 15,
|
"baseFontSize": 15,
|
||||||
"monospaceFontFamily": "FiraCode Nerd Font",
|
"monospaceFontFamily": "FiraCode Nerd Font",
|
||||||
"nativeMenus": false,
|
"nativeMenus": false,
|
||||||
"showRibbon": false,
|
"showRibbon": true,
|
||||||
"showViewHeader": false
|
"showViewHeader": false
|
||||||
}
|
}
|
||||||
Vendored
+3
-3
@@ -3,9 +3,9 @@
|
|||||||
"global-search": true,
|
"global-search": true,
|
||||||
"switcher": true,
|
"switcher": true,
|
||||||
"graph": true,
|
"graph": true,
|
||||||
"backlink": true,
|
"backlink": false,
|
||||||
"canvas": true,
|
"canvas": true,
|
||||||
"outgoing-link": true,
|
"outgoing-link": false,
|
||||||
"tag-pane": true,
|
"tag-pane": true,
|
||||||
"footnotes": true,
|
"footnotes": true,
|
||||||
"properties": true,
|
"properties": true,
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"bookmarks": true,
|
"bookmarks": true,
|
||||||
"markdown-importer": true,
|
"markdown-importer": true,
|
||||||
"zk-prefixer": false,
|
"zk-prefixer": false,
|
||||||
"random-note": true,
|
"random-note": false,
|
||||||
"outline": true,
|
"outline": true,
|
||||||
"word-count": true,
|
"word-count": true,
|
||||||
"slides": false,
|
"slides": false,
|
||||||
|
|||||||
Vendored
+1
-1
@@ -39,6 +39,6 @@
|
|||||||
"repelStrength": 6.34095634095634,
|
"repelStrength": 6.34095634095634,
|
||||||
"linkStrength": 0.504158004158004,
|
"linkStrength": 0.504158004158004,
|
||||||
"linkDistance": 250,
|
"linkDistance": 250,
|
||||||
"scale": 0.2882782914407446,
|
"scale": 0.21176529377630143,
|
||||||
"close": true
|
"close": true
|
||||||
}
|
}
|
||||||
+15
-2
@@ -1112,8 +1112,21 @@
|
|||||||
"artifact": "03-agent"
|
"artifact": "03-agent"
|
||||||
},
|
},
|
||||||
"pkmer": {
|
"pkmer": {
|
||||||
"tokenExpiresAt": 0,
|
"tokenExpiresAt": 1781624499398,
|
||||||
"userInfo": null
|
"userInfo": {
|
||||||
|
"sub": "fa484d57-e07b-48f3-9410-ff0ce39782d8",
|
||||||
|
"name": "gcdmitry",
|
||||||
|
"email": "gcdmitry@gmail.com",
|
||||||
|
"ai_quota": {
|
||||||
|
"quota": 49875000,
|
||||||
|
"usedQuota": 125000,
|
||||||
|
"remainingQuota": 49875000,
|
||||||
|
"remainingTokens": 99,
|
||||||
|
"requestCount": 1
|
||||||
|
},
|
||||||
|
"thino": false,
|
||||||
|
"supporter": false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"enableCustomModel": false,
|
"enableCustomModel": false,
|
||||||
"customModel": {
|
"customModel": {
|
||||||
|
|||||||
+12
-11
@@ -6,11 +6,11 @@
|
|||||||
"emojiStyle": "native",
|
"emojiStyle": "native",
|
||||||
"iconColor": null,
|
"iconColor": null,
|
||||||
"recentlyUsedIcons": [
|
"recentlyUsedIcons": [
|
||||||
"🧰",
|
"LiToolCase",
|
||||||
"🎒",
|
"LiLibrary",
|
||||||
"LiComputer",
|
"LiCalendar",
|
||||||
"💡",
|
"LiNotebook",
|
||||||
"🏔"
|
"LiLightbulb"
|
||||||
],
|
],
|
||||||
"recentlyUsedIconsSize": 5,
|
"recentlyUsedIconsSize": 5,
|
||||||
"rules": [],
|
"rules": [],
|
||||||
@@ -34,16 +34,16 @@
|
|||||||
"debugMode": false,
|
"debugMode": false,
|
||||||
"useInternalPlugins": false
|
"useInternalPlugins": false
|
||||||
},
|
},
|
||||||
"99 System": "🧰",
|
"99 System": "LiToolCase",
|
||||||
"99 System/Template": "LiBookTemplate",
|
"99 System/Template": "LiBookTemplate",
|
||||||
"99 System/Archive": "LiFolderArchive",
|
"99 System/Archive": "LiFolderArchive",
|
||||||
"99 System/Cache": "LiArchive",
|
"99 System/Cache": "LiArchive",
|
||||||
"02 Projects": "📔",
|
"02 Projects": "LiNotebook",
|
||||||
"03 Journal": "📅",
|
"03 Journal": "LiCalendar",
|
||||||
"02 Projects/Наука": "LiShieldQuestion",
|
"02 Projects/Наука": "LiShieldQuestion",
|
||||||
"02 Projects/Учеба": "LiNotebookTabs",
|
"02 Projects/Учеба": "LiNotebookTabs",
|
||||||
"01 Ideas": "💡",
|
"01 Ideas": "LiLightbulb",
|
||||||
"90 Library": "♎",
|
"90 Library": "LiLibrary",
|
||||||
"90 Library/02 DevOps": "LiSpline",
|
"90 Library/02 DevOps": "LiSpline",
|
||||||
"90 Library/04 Networking": "LiNetwork",
|
"90 Library/04 Networking": "LiNetwork",
|
||||||
"90 Library/05 Operating System": "LiComputer",
|
"90 Library/05 Operating System": "LiComputer",
|
||||||
@@ -67,5 +67,6 @@
|
|||||||
"01 Library/09 Sport": "LiDumbbell",
|
"01 Library/09 Sport": "LiDumbbell",
|
||||||
"01 Library/10 Finance": "LiCircleDollarSign",
|
"01 Library/10 Finance": "LiCircleDollarSign",
|
||||||
"01 Library/07 HomeLab": "LiServer",
|
"01 Library/07 HomeLab": "LiServer",
|
||||||
"01 Library/01 Admin": "🏔"
|
"01 Library/01 Admin": "🏔",
|
||||||
|
"00 Inbox": "LiInbox"
|
||||||
}
|
}
|
||||||
+2
-2
@@ -32,8 +32,8 @@
|
|||||||
"shiba-theme-settings@@translucent-pane-style-settings": "shib-setting-default-frosted-glass",
|
"shiba-theme-settings@@translucent-pane-style-settings": "shib-setting-default-frosted-glass",
|
||||||
"shiba-theme-settings@@shib-transparent-setting-panel": false,
|
"shiba-theme-settings@@shib-transparent-setting-panel": false,
|
||||||
"Plugin@@colorful-checkbox": true,
|
"Plugin@@colorful-checkbox": true,
|
||||||
"anuppuccin-theme-settings@@anuppuccin-theme-dark": "ctp-macchiato",
|
"anuppuccin-theme-settings@@anuppuccin-theme-dark": "ctp-mocha",
|
||||||
"anuppuccin-theme-settings@@anuppuccin-theme-accents": "ctp-accent-sapphire",
|
"anuppuccin-theme-settings@@anuppuccin-theme-accents": "ctp-accent-lavender",
|
||||||
"anuppuccin-theme-settings@@anp-active-line": "anp-current-line-border-only",
|
"anuppuccin-theme-settings@@anp-active-line": "anp-current-line-border-only",
|
||||||
"anuppuccin-theme-settings@@anp-pdf-blend-toggle-dark": false,
|
"anuppuccin-theme-settings@@anp-pdf-blend-toggle-dark": false,
|
||||||
"anuppuccin-theme-settings@@anp-alt-tab-style": "anp-alternate-tab-toggle",
|
"anuppuccin-theme-settings@@anp-alt-tab-style": "anp-alternate-tab-toggle",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: CI/CD — основы
|
title: CI/CD — основы
|
||||||
status: seed
|
status: processing
|
||||||
type: concept
|
type: concept
|
||||||
tags:
|
tags:
|
||||||
- devops
|
- devops
|
||||||
@@ -8,11 +8,13 @@ tags:
|
|||||||
- gitlab-ci
|
- gitlab-ci
|
||||||
- jenkins
|
- jenkins
|
||||||
created: 2026-06-12
|
created: 2026-06-12
|
||||||
updated: 2026-06-12
|
updated: 2026-06-16
|
||||||
aliases:
|
aliases:
|
||||||
- Основы CI/CD
|
- Основы CI/CD
|
||||||
- Непрерывная интеграция и доставка
|
- Непрерывная интеграция и доставка
|
||||||
source: "CIСD Конвейер для сборки и доставки продукта. Знакомство с GitlabCI и Jenkins"
|
source:
|
||||||
|
- "CIСD Конвейер для сборки и доставки продукта. Знакомство с GitlabCI и Jenkins"
|
||||||
|
- "[[CI-CD Конвейер для сборки и доставки продукта. Построение пайплайнов]]"
|
||||||
---
|
---
|
||||||
|
|
||||||
# CI/CD — Основы
|
# CI/CD — Основы
|
||||||
@@ -50,6 +52,7 @@ Unit-тесты запускают отдельные единицы кода в
|
|||||||
Delivery и deployment часто обозначают одной аббревиатурой CD, но уровень автоматизации выпуска у них различается.
|
Delivery и deployment часто обозначают одной аббревиатурой CD, но уровень автоматизации выпуска у них различается.
|
||||||
|
|
||||||
![[Pasted image 20260612125409.png]]
|
![[Pasted image 20260612125409.png]]
|
||||||
|
|
||||||
## Пайплайн
|
## Пайплайн
|
||||||
|
|
||||||
**Pipeline** — описанная последовательность стадий и задач CI/CD. Обычно новый запуск инициируется коммитом, merge request, тегом, расписанием или ручным действием.
|
**Pipeline** — описанная последовательность стадий и задач CI/CD. Обычно новый запуск инициируется коммитом, merge request, тегом, расписанием или ручным действием.
|
||||||
@@ -68,6 +71,32 @@ Delivery и deployment часто обозначают одной аббреви
|
|||||||
|
|
||||||
Отдельная задача внутри pipeline обычно называется **job**. Jobs объединяются в stages и выполняются на выделенных исполнителях.
|
Отдельная задача внутри pipeline обычно называется **job**. Jobs объединяются в stages и выполняются на выделенных исполнителях.
|
||||||
|
|
||||||
|
Stages задают крупные фазы процесса, например `build`, `test`, `deploy`. Jobs внутри разных stages обычно выполняются последовательно по порядку стадий, а несколько jobs внутри одной stage могут выполняться параллельно, если есть свободные исполнители.
|
||||||
|
|
||||||
|
Минимальный пример `.gitlab-ci.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
build-job:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- make build
|
||||||
|
|
||||||
|
test-job:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- make test
|
||||||
|
|
||||||
|
deploy-job:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- make deploy
|
||||||
|
```
|
||||||
|
|
||||||
## Артефакт
|
## Артефакт
|
||||||
|
|
||||||
**Артефакт** — неизменяемый результат сборки, который можно проверить, хранить и продвигать между средами. Это может быть бинарный файл, пакет, архив или контейнерный образ.
|
**Артефакт** — неизменяемый результат сборки, который можно проверить, хранить и продвигать между средами. Это может быть бинарный файл, пакет, архив или контейнерный образ.
|
||||||
@@ -103,6 +132,17 @@ Pipeline не заканчивается фактом развёртывания
|
|||||||
|
|
||||||
Jobs выполняются компонентами **GitLab Runner**. Runner может работать на физическом сервере, виртуальной машине, в Docker или Kubernetes.
|
Jobs выполняются компонентами **GitLab Runner**. Runner может работать на физическом сервере, виртуальной машине, в Docker или Kubernetes.
|
||||||
|
|
||||||
|
Конфигурация GitLab CI/CD сочетает декларативный YAML и команды shell. YAML описывает stages, jobs, переменные, зависимости и условия запуска. Shell-команды внутри `script` выполняют конкретные действия: сборку, тесты, публикацию артефактов или деплой.
|
||||||
|
|
||||||
|
Pipeline можно запускать:
|
||||||
|
|
||||||
|
- вручную через веб-интерфейс или API;
|
||||||
|
- автоматически по push, commit, merge request, tag и другим событиям;
|
||||||
|
- через webhook или интеграцию с внешней системой;
|
||||||
|
- с параметрами: переменными, выбранной веткой, тегом, коммитом или окружением.
|
||||||
|
|
||||||
|
В сложных проектах pipeline могут быть составными: родительский pipeline запускает дочерние pipeline, а отдельные части процесса выполняются последовательно или параллельно. Это помогает разделять сборку, тестирование, деплой и проверки безопасности по независимым конфигурациям.
|
||||||
|
|
||||||
Преимущества:
|
Преимущества:
|
||||||
|
|
||||||
- тесная интеграция с репозиториями, merge request и правами GitLab;
|
- тесная интеграция с репозиториями, merge request и правами GitLab;
|
||||||
@@ -121,6 +161,19 @@ Jobs выполняются компонентами **GitLab Runner**. Runner
|
|||||||
|
|
||||||
**Jenkins** — самостоятельный open-source сервер автоматизации. Pipeline можно описывать декларативно или программно в `Jenkinsfile` с использованием Groovy.
|
**Jenkins** — самостоятельный open-source сервер автоматизации. Pipeline можно описывать декларативно или программно в `Jenkinsfile` с использованием Groovy.
|
||||||
|
|
||||||
|
Для Jenkins важна экосистема плагинов: через них подключаются системы контроля версий, учётные данные, агенты, уведомления, Kubernetes, инструменты безопасности и observability. Плагины упрощают интеграции, но требуют регулярного обновления и контроля совместимости.
|
||||||
|
|
||||||
|
Pipeline можно настроить через веб-интерфейс, но для воспроизводимости лучше хранить `Jenkinsfile` в репозитории рядом с кодом. Тогда изменения pipeline проходят тот же контроль, что и изменения продукта: review, merge request, история коммитов и ограничения доступа.
|
||||||
|
|
||||||
|
Запуск Jenkins pipeline возможен:
|
||||||
|
|
||||||
|
- вручную через веб-интерфейс или API;
|
||||||
|
- автоматически через интеграции и плагины;
|
||||||
|
- через webhook из GitLab или другой VCS-платформы;
|
||||||
|
- по расписанию.
|
||||||
|
|
||||||
|
При описании pipeline на Groovy Jenkins может генерировать фрагменты синтаксиса через встроенный помощник. Это снижает риск ошибки при работе с параметрами шагов и плагинов.
|
||||||
|
|
||||||
Преимущества:
|
Преимущества:
|
||||||
|
|
||||||
- большая экосистема плагинов;
|
- большая экосистема плагинов;
|
||||||
@@ -135,6 +188,19 @@ Jobs выполняются компонентами **GitLab Runner**. Runner
|
|||||||
- конфликты версий и накопление плагинов усложняют обновления;
|
- конфликты версий и накопление плагинов усложняют обновления;
|
||||||
- для небольших команд эксплуатация может быть тяжелее встроенного решения.
|
- для небольших команд эксплуатация может быть тяжелее встроенного решения.
|
||||||
|
|
||||||
|
## Развитие CI/CD
|
||||||
|
|
||||||
|
CI/CD внедряют поэтапно: сначала автоматизируют сборку и базовые проверки, затем добавляют доставку артефактов, деплой, наблюдаемость и проверки безопасности. Процесс не должен останавливаться после первого рабочего pipeline.
|
||||||
|
|
||||||
|
Практики, которые усиливают CI/CD:
|
||||||
|
|
||||||
|
- [[IaC - основы]] — декларативное описание инфраструктуры и конфигурации;
|
||||||
|
- контейнеризация через [[Основы - Docker]] и оркестрация через [[Kubernetes - MOC]];
|
||||||
|
- observability: метрики, логи, трассировки, аудит, обработка логов и обратная связь;
|
||||||
|
- DevSecOps-проверки: SAST, SCA, DAST, IAST, RASP, сканирование инфраструктуры и работа с секретами.
|
||||||
|
|
||||||
|
Эти проверки можно добавлять как отдельные stages или jobs. Главное - не превращать pipeline в непрозрачный набор ручных действий: сборка, тесты, доставка и контроль качества должны быть описаны как код и воспроизводиться одинаково.
|
||||||
|
|
||||||
## GitLab CI/CD и Jenkins
|
## GitLab CI/CD и Jenkins
|
||||||
|
|
||||||
| Критерий | GitLab CI/CD | Jenkins |
|
| Критерий | GitLab CI/CD | Jenkins |
|
||||||
@@ -153,5 +219,6 @@ Jobs выполняются компонентами **GitLab Runner**. Runner
|
|||||||
- [[DevOps - основы]]
|
- [[DevOps - основы]]
|
||||||
- [[Git]]
|
- [[Git]]
|
||||||
- [[Gitlab]]
|
- [[Gitlab]]
|
||||||
|
- [[IaC - основы]]
|
||||||
- [[Основы - Docker]]
|
- [[Основы - Docker]]
|
||||||
- [[Kubernetes - MOC]]
|
- [[Kubernetes - MOC]]
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
title: Defense-in-depth
|
||||||
|
status: seed
|
||||||
|
type: concept
|
||||||
|
tags:
|
||||||
|
- networking
|
||||||
|
- security
|
||||||
|
- infosec
|
||||||
|
- defense-in-depth
|
||||||
|
created: 2026-06-16
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases:
|
||||||
|
- DiD
|
||||||
|
- Глубокоэшелонированная защита
|
||||||
|
source: "[[Безопасность инфраструктуры. ZTNA, SASE, DiD]]"
|
||||||
|
---
|
||||||
|
# Defense-in-depth
|
||||||
|
|
||||||
|
**Defense-in-depth** (**DiD**) — глубокоэшелонированная защита. Идея в том, чтобы строить несколько независимых слоёв контроля, которые замедляют злоумышленника и ограничивают ущерб при компрометации одного слоя.
|
||||||
|
|
||||||
|
Концепция заимствована из военной стратегии: защита не должна зависеть от одного барьера.
|
||||||
|
|
||||||
|
## Контуры защиты
|
||||||
|
|
||||||
|
DiD делит защиту на три группы.
|
||||||
|
|
||||||
|
**Физическая защита**:
|
||||||
|
|
||||||
|
- охрана и охранные системы;
|
||||||
|
- СКУД;
|
||||||
|
- видеонаблюдение;
|
||||||
|
- сигнализация;
|
||||||
|
- закрытые помещения, шкафы, замки, сейфы.
|
||||||
|
|
||||||
|
**Техническая защита**:
|
||||||
|
|
||||||
|
- контроль сетевого доступа;
|
||||||
|
- межсетевые экраны;
|
||||||
|
- антивирусная защита;
|
||||||
|
- прокси-серверы;
|
||||||
|
- системы аутентификации и авторизации.
|
||||||
|
|
||||||
|
**Административная защита**:
|
||||||
|
|
||||||
|
- политики доступа;
|
||||||
|
- регламенты обработки секретов;
|
||||||
|
- списки разрешённого и запрещённого ПО;
|
||||||
|
- правила работы с гостями, подрядчиками и внешними организациями.
|
||||||
|
|
||||||
|
## Практические направления
|
||||||
|
|
||||||
|
- ограничение привилегированного доступа;
|
||||||
|
- observability и аудит событий;
|
||||||
|
- культура DevSecOps;
|
||||||
|
- MFA;
|
||||||
|
- модели нулевого доверия;
|
||||||
|
- [[SASE]];
|
||||||
|
- централизованное управление секретами;
|
||||||
|
- безопасные практики разработки;
|
||||||
|
- регулярное сканирование инфраструктуры;
|
||||||
|
- сервисные сетки и [[Сегментация сети|сегментация]].
|
||||||
|
|
||||||
|
## Связь с ZTNA и SASE
|
||||||
|
|
||||||
|
[[Zero Trust Network Access|ZTNA]], [[SASE]] и DiD не конкурируют друг с другом. DiD задаёт общий принцип многоуровневой защиты, ZTNA описывает модель доступа, а SASE объединяет сетевой доступ и защитные сервисы в единую архитектуру.
|
||||||
|
|
||||||
|
## Связанные заметки
|
||||||
|
|
||||||
|
- [[Введение в сетевую безопасность]]
|
||||||
|
- [[Zero Trust Network Access]]
|
||||||
|
- [[SASE]]
|
||||||
|
- [[Сегментация сети]]
|
||||||
|
- [[Веб-аутентификация и авторизация]]
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
title: SASE
|
||||||
|
status: seed
|
||||||
|
type: concept
|
||||||
|
tags:
|
||||||
|
- networking
|
||||||
|
- security
|
||||||
|
- infosec
|
||||||
|
- sase
|
||||||
|
created: 2026-06-16
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases:
|
||||||
|
- Secure Access Service Edge
|
||||||
|
source: "[[Безопасность инфраструктуры. ZTNA, SASE, DiD]]"
|
||||||
|
---
|
||||||
|
# SASE
|
||||||
|
|
||||||
|
**SASE** (**Secure Access Service Edge**) — подход на стыке сетевого подключения и информационной безопасности. Его цель — дать безопасный и оптимальный доступ к IT-ресурсам независимо от того, где находятся пользователь, устройство и приложение.
|
||||||
|
|
||||||
|
Концепция SASE была сформулирована Gartner в 2019 году. Это не один продукт, а архитектурная идея конвергенции сетевого доступа и защитных сервисов.
|
||||||
|
|
||||||
|
## Предпосылки
|
||||||
|
|
||||||
|
- размытие сетевого периметра;
|
||||||
|
- рост облачных сервисов;
|
||||||
|
- удалённая работа;
|
||||||
|
- использование личных устройств;
|
||||||
|
- необходимость сочетать безопасность и качество канала.
|
||||||
|
|
||||||
|
## Базовые компоненты
|
||||||
|
|
||||||
|
- **[[Zero Trust Network Access|ZTNA]]** — доступ по модели нулевого доверия;
|
||||||
|
- **SD-WAN** — программно-управляемый выбор канала;
|
||||||
|
- **SWG** — безопасный веб-шлюз;
|
||||||
|
- **CASB** — брокер безопасного доступа к облачным сервисам;
|
||||||
|
- **FWaaS** — межсетевой экран как сервис.
|
||||||
|
|
||||||
|
## Дополнительные компоненты
|
||||||
|
|
||||||
|
Вендоры могут включать в SASE дополнительные механизмы:
|
||||||
|
|
||||||
|
- DLP;
|
||||||
|
- QoS;
|
||||||
|
- NGFW;
|
||||||
|
- [[VPN]];
|
||||||
|
- UEBA;
|
||||||
|
- антифрод;
|
||||||
|
- защиту DNS и Wi-Fi;
|
||||||
|
- инструменты обфускации.
|
||||||
|
|
||||||
|
## Связь с другими подходами
|
||||||
|
|
||||||
|
SASE включает [[Zero Trust Network Access|ZTNA]] как один из базовых компонентов. В рамках [[Defense-in-depth]] SASE можно рассматривать как один из архитектурных слоёв защиты доступа и сетевого трафика.
|
||||||
|
|
||||||
|
## Связанные заметки
|
||||||
|
|
||||||
|
- [[Введение в сетевую безопасность]]
|
||||||
|
- [[Zero Trust Network Access]]
|
||||||
|
- [[Defense-in-depth]]
|
||||||
|
- [[VPN]]
|
||||||
|
- [[Сегментация сети]]
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: Zero Trust Network Access
|
||||||
|
status: seed
|
||||||
|
type: concept
|
||||||
|
tags:
|
||||||
|
- networking
|
||||||
|
- security
|
||||||
|
- infosec
|
||||||
|
- ztna
|
||||||
|
created: 2026-06-16
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases:
|
||||||
|
- ZTNA
|
||||||
|
source: "[[Безопасность инфраструктуры. ZTNA, SASE, DiD]]"
|
||||||
|
---
|
||||||
|
# Zero Trust Network Access
|
||||||
|
|
||||||
|
**Zero Trust Network Access** (**ZTNA**) — модель доступа к ресурсам на основе принципа нулевого доверия. Решение о доступе принимается при каждом обращении к ресурсу, а не один раз после подключения к «доверенной» сети.
|
||||||
|
|
||||||
|
ZTNA не отменяет модель угроз, а меняет её: базовый сценарий предполагает, что сеть, устройство или пользователь уже могут быть скомпрометированы.
|
||||||
|
|
||||||
|
## Нулевое доверие
|
||||||
|
|
||||||
|
Концепция нулевого доверия исходит из того, что доверенных подключений нет. Доступ должен проверяться постоянно, с учётом контекста запроса, состояния пользователя, устройства и ресурса.
|
||||||
|
|
||||||
|
ZTNA часто выступает рыночной упаковкой Zero Trust, но сама концепция не привязана к конкретному вендору или технологии.
|
||||||
|
|
||||||
|
## Сценарии применения
|
||||||
|
|
||||||
|
- удалённый доступ с личных устройств;
|
||||||
|
- гостевой, подрядный и временный доступ;
|
||||||
|
- контроль IoT-устройств;
|
||||||
|
- доступ к внутренним ресурсам без прямого расширения периметра через [[VPN]].
|
||||||
|
|
||||||
|
## Контексты доступа
|
||||||
|
|
||||||
|
Для принятия решения ZTNA может учитывать:
|
||||||
|
|
||||||
|
- сетевой контекст: тип подключения, геолокация, источник запроса;
|
||||||
|
- контекст устройства: модель, ОС, обновления, состояние безопасности;
|
||||||
|
- пользовательский контекст: идентификация и аутентификация;
|
||||||
|
- ролевой контекст: авторизация и необходимые ресурсы;
|
||||||
|
- контекст безопасности: признаки компрометации, риск запроса, состояние учётной записи.
|
||||||
|
|
||||||
|
## Внедрение
|
||||||
|
|
||||||
|
Перед внедрением нужно определить сценарии применения, роли пользователей, ресурсы, контексты доступа и оценку рисков.
|
||||||
|
|
||||||
|
Практические первые шаги:
|
||||||
|
|
||||||
|
1. Включить MFA там, где это применимо.
|
||||||
|
2. Отказаться от статичного доверия к IP-адресам и allowlist.
|
||||||
|
3. Идентифицировать каждого пользователя и каждый запрос.
|
||||||
|
4. Сегментировать ресурсы и доступы к ним.
|
||||||
|
5. Постепенно убрать разделение пользователей на «своих» и «чужих».
|
||||||
|
|
||||||
|
## Связанные заметки
|
||||||
|
|
||||||
|
- [[Введение в сетевую безопасность]]
|
||||||
|
- [[SASE]]
|
||||||
|
- [[Defense-in-depth]]
|
||||||
|
- [[VPN]]
|
||||||
|
- [[Веб-аутентификация и авторизация]]
|
||||||
|
- [[Сегментация сети]]
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
---
|
---
|
||||||
status: seed
|
title: Введение в сетевую безопасность
|
||||||
|
status: processing
|
||||||
type: concept
|
type: concept
|
||||||
tags:
|
tags:
|
||||||
- networking
|
- networking
|
||||||
- security
|
- security
|
||||||
- infosec
|
- infosec
|
||||||
|
- vulnerability-scanning
|
||||||
created: 2025-12-17
|
created: 2025-12-17
|
||||||
updated: 2026-05-07
|
updated: 2026-06-16
|
||||||
aliases:
|
aliases:
|
||||||
- Введение в сетевую безопасность
|
- Введение в сетевую безопасность
|
||||||
|
source: "[[Безопасность инфраструктуры. ZTNA, SASE, DiD]]"
|
||||||
---
|
---
|
||||||
# Введение в сетевую безопасность
|
# Введение в сетевую безопасность
|
||||||
|
|
||||||
**Сетевая безопасность** — часть информационной безопасности, которая защищает сетевую инфраструктуру, передаваемые данные и доступ к сетевым ресурсам.
|
**Сетевая безопасность** — часть информационной безопасности, которая защищает сетевую инфраструктуру, передаваемые данные и доступ к сетевым ресурсам.
|
||||||
|
|
||||||
Сетевая безопасность связана с архитектурой сети, политиками доступа, сегментацией, мониторингом и защитой от атак. В контексте сети рядом лежат темы [[Сегментация сети]], [[VPN]] и [[Networking - MOC]].
|
Сетевая безопасность связана с архитектурой сети, политиками доступа, [[Сегментация сети|сегментацией]], мониторингом и защитой от атак. В контексте сети рядом лежат темы [[VPN]], [[Веб-аутентификация и авторизация]] и [[Networking - MOC]].
|
||||||
|
|
||||||
## CIA: базовая модель безопасности
|
## CIA: базовая модель безопасности
|
||||||
|
|
||||||
@@ -67,8 +70,42 @@ aliases:
|
|||||||
3. Отпечаток сравнивается с базой известных уязвимостей.
|
3. Отпечаток сравнивается с базой известных уязвимостей.
|
||||||
4. Формируется отчёт с найденными проблемами.
|
4. Формируется отчёт с найденными проблемами.
|
||||||
|
|
||||||
|
Сканеры инфраструктуры помогают находить слабые шифры, небезопасные протоколы, дефолтные пароли, открытые порты, устаревшие приложения, просроченные сертификаты и ошибки конфигурации. Практический смысл отчёта — не сам список находок, а приоритизация рисков и митигация по рекомендациям инструмента.
|
||||||
|
|
||||||
|
OpenVAS, современная ветка которого связана с Greenbone, можно развернуть в контейнере для учебной проверки подконтрольной инфраструктуры. Типовой сценарий:
|
||||||
|
|
||||||
|
1. Запустить OpenVAS/Greenbone.
|
||||||
|
2. Создать target с адресом проверяемого узла.
|
||||||
|
3. Создать scan task.
|
||||||
|
4. Дождаться статуса `Done`.
|
||||||
|
5. Разобрать findings и поле `Solution`.
|
||||||
|
|
||||||
|
## Архитектурные подходы
|
||||||
|
|
||||||
|
Отдельные подходы к защите инфраструктуры:
|
||||||
|
|
||||||
|
- [[Zero Trust Network Access]] — модель доступа на основе нулевого доверия.
|
||||||
|
- [[SASE]] — конвергенция сетевого доступа и защитных сервисов.
|
||||||
|
- [[Defense-in-depth]] — многоуровневая защита, где компрометация одного слоя не должна ломать всю систему.
|
||||||
|
|
||||||
|
## Источники
|
||||||
|
|
||||||
|
- [Базовое описание ZTNA](https://codeby.net/threads/chto-takoye-ztna-i-zachem-on-nuzhen.84541/)
|
||||||
|
- [Zero Trust архитектура в 2025 году](https://securitymedia.org/info/zero-trust-arkhitektura-v-2025-godu-printsipy-kontseptsii-nulevogo-doveriya-i-ee-razvitie.html)
|
||||||
|
- [Практические рекомендации по переходу к Zero Trust](https://www.kaspersky.ru/blog/zero-trust-transition-practical-advice/39484/)
|
||||||
|
- [SASE и ИИ](https://habr.com/ru/articles/914496/)
|
||||||
|
- [Defense-in-depth на примере XZ Utils](https://www.wiz.io/academy/defense-in-depth/)
|
||||||
|
- [Defense-in-depth: уровни контроля](https://www.wallarm.com/what/defense-in-depth-concept/)
|
||||||
|
- [Defense-in-depth](https://www.fortinet.com/resources/cyberglossary/defense-in-depth/)
|
||||||
|
- [Modern defense-in-depth strategies](https://www.isaca.org/resources/news-and-trends/isaca-now-blog/2025/beyond-the-moat-modern-defense-in-depth-strategies/)
|
||||||
|
|
||||||
## Связанные заметки
|
## Связанные заметки
|
||||||
|
|
||||||
- [[Networking - MOC]]
|
- [[Networking - MOC]]
|
||||||
- [[Сегментация сети]]
|
- [[Сегментация сети]]
|
||||||
- [[VPN]]
|
- [[VPN]]
|
||||||
|
- [[Веб-аутентификация и авторизация]]
|
||||||
|
- [[CA и NGINX]]
|
||||||
|
- [[Zero Trust Network Access]]
|
||||||
|
- [[SASE]]
|
||||||
|
- [[Defense-in-depth]]
|
||||||
|
|||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
status: seed
|
||||||
|
type: concept
|
||||||
|
tags: []
|
||||||
|
created: 2025-12-17
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI-CD Конвейер для сборки и доставки продукта. Построение пайплайнов
|
||||||
|
## Часть 2. Пайплайны в GitlabCI
|
||||||
|
|
||||||
|
Пайплайны в GitlabCI строятся на основе языков **Shell** и **Yaml**. С помощью них можно описывать этапы процессов CI/CD и их желаемое поведение.
|
||||||
|
|
||||||
|
Язык `yaml` позволяет описывать конфигурацию этапов в декларативном ключе. Основной файл, в котором описывается работа пайплайна, носит название `Gitlab-ci.yml`.
|
||||||
|
|
||||||
|
### Shell
|
||||||
|
|
||||||
|
В GitlabCI при описании этапов пайплайна можно задействовать `shell`.
|
||||||
|
|
||||||
|
> **Shell** — командный интерпретатор операционных систем семейства Unix.
|
||||||
|
|
||||||
|
Shell позволяет существенно расширить возможности описания процессов CI/CD.
|
||||||
|
|
||||||
|
### Stage
|
||||||
|
|
||||||
|
Структура файла `Gitlab-ci.yml` предполагает разбиение пайплайна на этапы (`Stages`).
|
||||||
|
Каждый Stage представляет из себя этап пайплайна с полным описанием его работы.
|
||||||
|
|
||||||
|
Этапы (Stages) могут выполняться **последовательно** или **параллельно**.
|
||||||
|
|
||||||
|
Так могут выглядеть типовые этапы (Stages) в пайплайне GitlabCI.
|
||||||
|
```yaml
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
build-job:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- do some build
|
||||||
|
|
||||||
|
test-job:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- do some test
|
||||||
|
|
||||||
|
deploy-job:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- do some deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
**Запуск пайплайна можно инициировать тремя способами:**
|
||||||
|
|
||||||
|
- *Вручную* (через веб-интерфейс или API).
|
||||||
|
- *Автоматически* по событию (Push, Commit, Merge-request, etc) - как триггеры в PSQL
|
||||||
|
- Используя *Web-hook*.
|
||||||
|
|
||||||
|
**Как ручной, так и автоматический запуск пайплайнов может быть дополнительно параметризован при запуске:**
|
||||||
|
|
||||||
|
- Указание специфических переменных.
|
||||||
|
- Выбор учетных данных (Credentials).
|
||||||
|
- Сборка по конкретному коммиту, ветке, тэгу.
|
||||||
|
- .. и многое другое
|
||||||
|
|
||||||
|
**Вложенные пайплайны:**
|
||||||
|
|
||||||
|
В зависимости от сложности процессов CI/CD, пайплайны могут быть составными — содержать вложенные пайплайны, выполняющиеся последовательно или параллельно.
|
||||||
|
|
||||||
|
**Интеграция:**
|
||||||
|
|
||||||
|
В случае использования в процессах CI/CD нескольких инструментов, возможна их интеграция (к примеру, исходный код хранится в gitlab, а пайплайны запускаются в Jenkins) через встроенный в GitlabCI механизм интеграций.
|
||||||
|
|
||||||
|
**GitlabCI имеет понятный веб интерфейс, в котором отображаются** **результаты прохождения пайплайнов и их отдельных этапов** **в простом и доступном виде:**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Часть 3. Пайплайны в Jenkins
|
||||||
|
|
||||||
|
Для полноценной работы Jenkins будет достаточно предустановленных плагинов, но наличие минимально необходимых дополнительных плагинов существенно **облегчит** построение и модификацию пайплайнов.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Не забывайте своевременно производить обновление используемых плагинов, чтобы избежать ошибок и уязвимостей.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
После установки плагина, его требуется настроить для работы, сделать это можно в настройках Jenkins:
|
||||||
|
|
||||||
|
- Глобальные настройки.
|
||||||
|
- Настройки инструментов.
|
||||||
|
- Настройки безопасности.
|
||||||
|
- Конфигурирование сред исполнения.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Настройки проекта в Jenkins сводятся к конфигурации самого проекта и его интеграций, а также настройке пайплайна.
|
||||||
|
|
||||||
|
Для описания поведения пайплайна мы можем использовать графический интерфейс или язык **Groovy**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Если вы испытываете трудности с языком Groovy — вам придет на помощь встроенный редактор запросов.
|
||||||
|
|
||||||
|
Сгенерированный запрос можно использовать в описании вашего пайплайна.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Хорошей практикой является описание пайплайна в отдельном файле, который хранится не в Jenkins и подвергается защите от несанкционированного внесения изменений.
|
||||||
|
|
||||||
|
Такой файл часто носит название Jenkinsfile и хранится в репозитории (например, в Gitlab), а не в самом Jenkins.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Хранящийся в репозитории Jenkinsfile, вместе с самим исходным кодом продукта, защищен от изменений (их могут вносить только уполномоченные члены команды или с использованием процедуры Merge-request).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Инициировать процедуру запуска пайплайна можно несколькими способами:
|
||||||
|
|
||||||
|
- Вручную (веб-интерфейс или API).
|
||||||
|
- Автоматически, с использованием интеграций (посредством плагинов).
|
||||||
|
- Используя Web-hook.
|
||||||
|
- С помощью планировщика сборок.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Имея опыт работы с языком **Groovy**, используя разнообразные плагины и следуя хорошим практикам организации пайплайнов, можно добиться грамотного построения процессов CI/CD:
|
||||||
|
|
||||||
|
- Параметризованные сборки.
|
||||||
|
- Хранение Jenkinsfile в репозитории с ограничением на изменение.
|
||||||
|
- Интеграция с Git, Kubernetes, Инструментами обеспечения информационной безопасности и Observability.
|
||||||
|
- Отказоустойчивая и безопасная конфигурация доступа к Jenkins для других инструментов.
|
||||||
|
- Визуализация процессов.
|
||||||
|
|
||||||
|
## Часть 4. Внедрение и развитие процессов CI/CD
|
||||||
|
|
||||||
|
## Внедрение и развитие процессов CI/CD
|
||||||
|
|
||||||
|
В зависимости от того, в каком состоянии находятся существующие процессы разработки, тестирования и администрирования, процессы CI/CD должны внедряться предсказуемо и поэтапно.
|
||||||
|
|
||||||
|
Любые существующие процессы CI/CD должны развиваться не прекращаясь.
|
||||||
|
|
||||||
|
**Обогатить имеющиеся процессы непрерывной сборки и доставки** **можно следующими практиками:**
|
||||||
|
|
||||||
|
- IaC.
|
||||||
|
- Orchestration.
|
||||||
|
- Observability.
|
||||||
|
- Info-security.
|
||||||
|
|
||||||
|
При настройке процессов непрерывной доставки используйте подход декларативного описания инфраструктуры и конфигурации в виде кода (практики IaC).
|
||||||
|
|
||||||
|
Старайтесь придерживаться микросервисной архитектуры там, где это оправдано.
|
||||||
|
|
||||||
|
Ориентируйтесь на контейнерные технологии.
|
||||||
|
|
||||||
|
Изучайте и применяйте технологии оркестрации контейнерных и неконтейнерных рабочих нагрузок в режиме кластера.
|
||||||
|
|
||||||
|
**Не пренебрегайте развитием практик обеспечения Observability:**
|
||||||
|
|
||||||
|
- Мониторинг.
|
||||||
|
- Трассировки.
|
||||||
|
- Аудит.
|
||||||
|
- Журналирование.
|
||||||
|
- Обработка логов.
|
||||||
|
- Сбор обратной связи.
|
||||||
|
|
||||||
|
Дополняйте этими практиками этапы своих пайплайнов, или добавляйте новые этапы на их основе.
|
||||||
|
|
||||||
|
**Существующие практики CI/CD можно и нужно обогащать** **внедрением проверок по информационной безопасности из** **методологии Devsecops:**
|
||||||
|
|
||||||
|
- Различные виды анализа (SAST, SCA, DAST, IAST, RASP).
|
||||||
|
- Сканирование инфраструктуры.
|
||||||
|
- Использование профильных инструментов для работы с сенсетивной информацией.
|
||||||
|
|
||||||
|
Помните о том, что процессы CI/CD — это лишь одна из множества Devops-практик, которые важно и нужно применять для создания благоприятной атмосферы в коллективной работе над цифровым продуктом.
|
||||||
|
|
||||||
|
**Devops** — это, в первую очередь, культура взаимодействия всех участников жизненного цикла цифрового продукта.
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
---
|
||||||
|
status: seed
|
||||||
|
type: concept
|
||||||
|
tags: []
|
||||||
|
created: 2025-12-17
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# Безопасность инфраструктуры. ZTNA, SASE, DiD
|
||||||
|
|
||||||
|
## Zero trust network access
|
||||||
|
|
||||||
|
**ZTNA** - Zero Trust Network Access - давно известный подход по обеспечению безопасности, основанный на концепции нулевого доверия.
|
||||||
|
ZTNA является реинкарнацией старого подхода ZT, своеобразной рыночной упаковкой.
|
||||||
|
|
||||||
|
> [!note] Бесспорные факторы популярности подхода ZTNA:
|
||||||
|
> - Возрастающая популярность облачных технологий.
|
||||||
|
> - Массовый переход на удаленный формат работы (COVID-19).
|
||||||
|
|
||||||
|
### Нулевое доверие
|
||||||
|
Концепция **“нулевого доверия”** предполагает, что решение о предоставлении или непредоставлении доступа к ресурсу принимается каждый раз в момент самого обращения.
|
||||||
|
При сценарии “нулевого доверия” нет никаких доверенных подключений, поэтому моделирование угроз развивается изначально по худшему из возможных сценариев.
|
||||||
|
<u>*ZTNA* не отменяет существующую модель угроз, а изменяет ее</u>.
|
||||||
|
|
||||||
|
*Распространенные сценарии применения:*
|
||||||
|
1) Удаленный доступ с личных устройств
|
||||||
|
2) Гостевые и временные доступы
|
||||||
|
3) Контроль интернета вещей (IoT)
|
||||||
|
|
||||||
|
*Источники информации (контексты) для принятия решения о предоставлении или непредоставлении доступа в ZTNA:*
|
||||||
|
- Сетевой контекст (тип подключения, геолокация).
|
||||||
|
- Контекст устройства (модель, ОС, обновления, локализация).
|
||||||
|
- Пользовательский контекст (идентификация).
|
||||||
|
- Ролевой контекст (авторизация).
|
||||||
|
- Контекст безопасности (компрометация данных).
|
||||||
|
- .. и многое другие
|
||||||
|
|
||||||
|
*Внедрение ZTNA требует достаточных предварительных действий:*
|
||||||
|
1. Определить сценарии применения, контексты, оценку рисков.
|
||||||
|
2. Определить **роли** пользователей и необходимые им **ресурсы**.
|
||||||
|
3. Подобрать правильные, подходящие в вашем случае **инструменты**.
|
||||||
|
4. Поддержка вашего рабочего окружения.
|
||||||
|
5. Готовность внедрять ZTNA последовательно и поэтапно.
|
||||||
|
|
||||||
|
*Процесс внедрения ZTNA:*
|
||||||
|
1) Отказ от статических IP-адресов
|
||||||
|
2) Отказ от White-Lists
|
||||||
|
3) Идентифицировать каждого пользователя и каждый пользовательский запрос
|
||||||
|
4) Сегментировать имеющиеся ресурсы и доступы к ним
|
||||||
|
5) Отмена разделения пользователей на своих/чужих (гостей, подрядчиков, временных пользователей)
|
||||||
|
6) Внедрить многофакторную аутентификацию везде, где это применимо, но поэтапно.
|
||||||
|
|
||||||
|
**ZTNA** — это маркетинговая реинкарнация давно существующей модели "нулевого доверия".
|
||||||
|
|
||||||
|
Концептуально ZTNA не привязана к конкретной технологии или вендору.
|
||||||
|
|
||||||
|
Начать внедрять ZTNA можно и нужно поэтапно, начать достаточно с малого. К примеру, двух-факторная аутентификация пользователей поможет защититься более, чем от 70% потенциальных взломов*.
|
||||||
|
|
||||||
|
\**по версии журнала Anti-malware.*
|
||||||
|
|
||||||
|
## Secure access service edge
|
||||||
|
|
||||||
|
**SASE** — Secure Access Service Edge.
|
||||||
|
|
||||||
|
Достаточно новый подход, находящийся на стыке решения двух важных задач — обеспечения информационной безопасности и выбора оптимального способа подключения к ресурсам.
|
||||||
|
*SASE* является комплексным подходом и предоставляется множеством вендоров.
|
||||||
|
|
||||||
|
Концепция *SASE* разработана исследовательской компанией Gartner в 2019 году.
|
||||||
|
|
||||||
|
Главная цель *SASE* сформулирована следующим образом — сервис безопасного и оптимального доступа к IT-ресурсам (симбиоз выбора стабильного канала связи и обеспечения информационной безопасности ресурсов).
|
||||||
|
|
||||||
|
*Предпосылки возникновения SASE во многом схожи с ZTNA:*
|
||||||
|
- Размытие периметра инфраструктуры.
|
||||||
|
- Развитие облачных технологий.
|
||||||
|
- Массовый переход на удаленную работу.
|
||||||
|
- Использование личных устройств.
|
||||||
|
- COVID-19.
|
||||||
|
|
||||||
|
*Gartner составило список обязательных технологий, которые должны быть реализованы в SASE:*
|
||||||
|
|
||||||
|
- **ZTNA (Zero Trust Network Access)** - модель сетевого доступа к ресурсам на основе политики “нулевого доверия”.
|
||||||
|
- **SD-WAN (Software-Defined Wide Area Network)** — динамический подбор наилучшего способа подключения в зависимости от производительности.
|
||||||
|
- **SWG (Secure Web Gateway)** — безопасный сетевой шлюз.
|
||||||
|
- **CASB (Cloud Access Security Broker)** — брокер безопасного доступа к облачным ресурсам.
|
||||||
|
- **FWaaS (Firewall-as-a-Service)** — сервис облачного сетевого экрана.
|
||||||
|
|
||||||
|
*Помимо обязательных компонентов, вендоры SASE могут реализовывать дополнительные, необязательные компоненты:*
|
||||||
|
|
||||||
|
- **DLP** (data leak prevention) — предотвращение утечек.
|
||||||
|
- **QoS** (quality of service) — приоритизация трафика.
|
||||||
|
- **NGFW** (web application firewall).
|
||||||
|
- **VPN** (virtual private network) — виртуальная частная сеть.
|
||||||
|
- **UEBA** (user entity behaviour analysis) — поведенческий анализ.
|
||||||
|
- **Антифрод инструменты** — оценка вероятности финансового мошенничества.
|
||||||
|
- **Инструменты обфускации** — изменение исходного кода в целях затруднения анализа при сохранении функциональности.
|
||||||
|
- **Инструменты защиты DNS / WiFi от подмена и взлома.**
|
||||||
|
- и многие другие
|
||||||
|
|
||||||
|
Вендоры SASE-решений, реализовавшие основные компоненты, могут получить *сертификацию Gartner*.
|
||||||
|
Отечественных вендоров SASE-решений не существует до сих пор. (
|
||||||
|
|
||||||
|
*Прогноз популярности SASE по версии аналитиков Gartner:*
|
||||||
|
|
||||||
|
- **SASE** выйдет на плато *продуктивности* в течение 2024-2029.
|
||||||
|
- К 2024 году как минимум 40% предприятий будут иметь четкие стратегии по внедрению SASE (по сравнению с 1% на начало 2019 года).
|
||||||
|
- Вендоры будут переоценивать свои возможности по предоставлению услуг SASE.
|
||||||
|
|
||||||
|
Концепция SASE сформулирована компанией Gartner, она состоит из конвергентного решения по оптимизации доступа и информационной безопасности ИТ-ресурсов.
|
||||||
|
|
||||||
|
Реализовав список обязательных компонентов, вендор может претендовать на сертификацию своего решения у *Gartner*, реализовав как можно больше дополнительных компонентов эффективно конкурировать с другими вендорами.
|
||||||
|
|
||||||
|
## Defense-in-depth
|
||||||
|
|
||||||
|
**DiD** — Defense-in-depth.
|
||||||
|
|
||||||
|
DiD является концепцией глубокой защиты, в его основу заложены несколько степеней защиты, методы которых не пересекаются.
|
||||||
|
Подход призван задержать и усложнить продвижение и действия злоумышленника.
|
||||||
|
Концепция глубокоэшелонированной защиты заимствована из общей военной стратегии и разработана агентством национальной безопасности США.
|
||||||
|
|
||||||
|
*DiD делит организацию защиты инфраструктуры на три контролируемые части:*
|
||||||
|
|
||||||
|
- Физическая средства защиты.
|
||||||
|
- Технические средства защиты.
|
||||||
|
- Административные средства защиты.
|
||||||
|
|
||||||
|
*К физическим средствам защиты можно отнести:*
|
||||||
|
- Охранники и охранные системы.
|
||||||
|
- Системы контроля и управления доступом (СКУД)
|
||||||
|
- Видеонаблюдение (CCTV)
|
||||||
|
- Сигнализационные системы.
|
||||||
|
- Запертые двери, шкафы, замки, сейфы.
|
||||||
|
|
||||||
|
*К техническим средствам защиты можно отнести:*
|
||||||
|
- Контроль сетевого доступа.
|
||||||
|
- Межсетевые экраны.
|
||||||
|
- Антивирусная защита.
|
||||||
|
- Прокси-серверы.
|
||||||
|
- Системы аутентификации и авторизации.
|
||||||
|
|
||||||
|
*К административным средствам защиты можно отнести:*
|
||||||
|
- Регулирование и управления самих средств защиты (документы и нормативка?)
|
||||||
|
- Обработка сенсетивной информации (пароли, токены и т.д.)
|
||||||
|
- Ведение списков разрешенных и запрещенных ПО.
|
||||||
|
- Политики взаимодействия с гостевыми допусками, внешними ресурсами и организациями.
|
||||||
|
|
||||||
|
*С чего начать внедрение DiD:*
|
||||||
|
- Включение защиты от привилегированного доступа.
|
||||||
|
- Использование средств *Observability*.
|
||||||
|
- Развитие культуры DevSecOps.
|
||||||
|
- Внедрение многофакторной аутентификации.
|
||||||
|
- Использование моделей “*нулевого доверия*”.
|
||||||
|
- Внедрение *SASE*-решений.
|
||||||
|
- Использование безопасных инструментов разработки и практик (секьюрные заголовки в запросах, защита куки, централизованное управление секретами и паролями).
|
||||||
|
- Своевременный аудит безопасности.
|
||||||
|
- Использование сервисных сеток и инфраструктурных сканеров.
|
||||||
|
|
||||||
|
> [!note] DiD
|
||||||
|
> - Концепция Defense-in-depth основывается на военных принципах глубокоэшелонированной защиты.
|
||||||
|
> - DiD разделяет защиту на три независимых контура — физическую, техническую и административную.
|
||||||
|
> - Три степени защиты призваны замедлить продвижение потенциального злоумышленника.
|
||||||
|
|
||||||
|
## Сканеры инфраструктуры
|
||||||
|
|
||||||
|
Для выполнения сканирования подконтрольной инфраструктуры на предмет уязвимостей используются **разнообразные** **инфраструктурные сканеры**, позволяющие обнаруживать:
|
||||||
|
- Наборы слабых шифров.
|
||||||
|
- Небезопасные конфигурации и протоколы.
|
||||||
|
- Стандартные и дефолтные пароли, порты.
|
||||||
|
- Не обновленные приложения и библиотеки.
|
||||||
|
- Просроченные и скомпрометированные сертификаты.
|
||||||
|
|
||||||
|
Примером проверенных инфраструктурных сканеров может быть opensource-решение “*Openvas*” от сообщества “Greenbone”.
|
||||||
|
|
||||||
|
![[Pasted image 20260616205715.png|737]]
|
||||||
|
|
||||||
|
## Часть 6. Рефлексия и практическая работа
|
||||||
|
|
||||||
|
### Проверка достижения целей урока
|
||||||
|
|
||||||
|
1. Познакомились с основными принципами обеспечения безопасности стека приложений и инфраструктуры.
|
||||||
|
2. Узнали, как связаны ZTNA, SASE и DiD друг с другом.
|
||||||
|
3. Познакомились с инфраструктурными сканерами.
|
||||||
|
4. Закрепили на практике установку и работу с инструментом сканирования инфраструктуры.
|
||||||
|
5. На примере рассмотрели закрытие потенциальной уязвимости в Nginx.
|
||||||
|
|
||||||
|
### Практическая работа
|
||||||
|
|
||||||
|
1. Самостоятельно проинсталлировать инфраструктурный сканер.
|
||||||
|
2. Самостоятельно выполнить сканирование инфраструктуры.
|
||||||
|
3. Изучить список литературы.
|
||||||
|
|
||||||
|
#### Самостоятельно происталировать инфраструктурный сканер
|
||||||
|
|
||||||
|
- Ознакомьтесь с методическим материалом по развертыванию и использованию инфраструктурного сканера.
|
||||||
|
- Выполните действия, описанные в методическом материале.
|
||||||
|
- Установите инфраструктурный сканер.
|
||||||
|
|
||||||
|
#### Самостоятельно выполнить сканирование инфраструктуры
|
||||||
|
|
||||||
|
- Используя установленный в задании #1 инфраструктурный сканер, выполните сканирование подконтрольной инфраструктуры.
|
||||||
|
- Убедитесь, что сканирование успешно завершено и самостоятельно изучите отчет о сканировании.
|
||||||
|
|
||||||
|
#### Изучить список литературы
|
||||||
|
|
||||||
|
Самостоятельно изучите приложенный список дополнительной литературы.
|
||||||
|
|
||||||
|
### Методический материал
|
||||||
|
|
||||||
|
Развёртывание и использование инфраструктурного сканера Для начала работ вам понадобится docker.
|
||||||
|
|
||||||
|
Подробная установка docker рассматривалась в методических материалах к предыдущим урокам.
|
||||||
|
|
||||||
|
Запустим контейнер с инструментом openvas, используя image пользователя mikesplain:
|
||||||
|
|
||||||
|
$ docker run -d -p 443:443 -p 9390:9390 --name openvas
|
||||||
|
mikesplain/openvas
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Убедимся в том, что контейнер успешно запущен:
|
||||||
|
|
||||||
|
$ docker ps
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Контейнер запущен успешно, в браузере выполним переход в веб-интерфейс развернутого инструмента openvas:
|
||||||
|
|
||||||
|
_browser: https://localhost/_
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Авторизуемся, используя административные учетные данные по умолчанию (admin:admin):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Переходим в раздел "Scan", выбираем в выпадающем меню поле "Tasks":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
В верхнем левом углу нажимаем на кнопку со звездочкой и выбираем "New Task":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Справа от поля "Scan Target" нажимаем на кнопку со звездочкой и переходим в интерфейс указания нового target для сканирования:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Далее указываем наименование target и ip-address (в моем случае это localhost, если вы сканируете инфраструктуру, убедитесь, что обеспечен сетевой доступ по портам 22, 80, 443 для полноты возможностей сканирования):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Сохраняем изменение, нажав кнопку "Create". Убедимся, что в настройках Task подставился созданный target:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Нажимаем кнопку "Create". Созданный Task отображается в списке tasks, но еще не запущен.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Для запуска Task нажимаем кнопку начала сканирования ("Start") в разделе "Actions" нашего Task. Состояние Task изменилось на "Requested":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Через некоторое время обновим страницу и убедимся, что строка состояния нашего Task изменяется на количество процентов от выполненного сканирования:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Сканирование может занять продолжительное время.
|
||||||
|
|
||||||
|
Дождемся завершения сканирования, убедившись, что статус сканирования изменился на "Done":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Перейдем к результатам сканирования, нажав на статус сканирования:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
В открывшемся отчете можем ознакомиться со всеми результатами сканирования, нажав на них и перейдя в подробное описание:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Подобным образом можем отработать все срабатывания, предприняв необходимые действия по их устранению. Полезно принимать в расчет рекомендации по митигации обнаруженных рисков (поле "Solution")
|
||||||
|
|
||||||
|
#### Список дополнительной литературы
|
||||||
|
|
||||||
|
[Базовое русскоязычное описание модели доступа ZTNA](https://codeby.net/threads/chto-takoye-ztna-i-zachem-on-nuzhen.84541/)
|
||||||
|
[Обзор эволюции концепции ZTNA в мультиоблачных средах](https://securitymedia.org/info/zero-trust-arkhitektura-v-2025-godu-printsipy-kontseptsii-nulevogo-doveriya-i-ee-razvitie.html)
|
||||||
|
[Практические рекомендации по первой фазе реализации ZTNA](https://www.kaspersky.ru/blog/zero-trust-transition-practical-advice/39484/)
|
||||||
|
[Влияние ИИ на адаптивность и безопасность решений SASE](https://habr.com/ru/articles/914496/)
|
||||||
|
[Кейс по уязвимости XZ Utils и практической ценности DiD](https://www.wiz.io/academy/defense-in-depth/)
|
||||||
|
[Разбор уровней контроля (физический, технический, административный)](https://www.wallarm.com/what/defense-in-depth-concept/)
|
||||||
|
[Современные мультислойные меры безопасности](https://www.fortinet.com/resources/cyberglossary/defense-in-depth/)
|
||||||
|
[Концепция многоуровневой защиты, сегментация, ограничение ущерба, адаптивность защиты](https://www.isaca.org/resources/news-and-trends/isaca-now-blog/2025/beyond-the-moat-modern-defense-in-depth-strategies/)
|
||||||
|
[Обзор новых ИБ-продуктов весны 2025](https://www.itsec.ru/articles/obzor-novyh-ib-produktov-vesny-2025-g/)
|
||||||
|
[Разбор трех технологических волн: AI-инструменты, DevSecOps, Platform Engineering](https://habr.com/ru/companies/oleg-bunin/articles/887316/)
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 557 KiB |
+17
-6
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
generated: 2026-06-15
|
generated: 2026-06-16
|
||||||
total: 514
|
total: 520
|
||||||
---
|
---
|
||||||
|
|
||||||
# Vault Index
|
# Vault Index
|
||||||
@@ -9,13 +9,13 @@ total: 514
|
|||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| Всего заметок | 514 |
|
| Всего заметок | 520 |
|
||||||
|
|
||||||
## .
|
## .
|
||||||
|
|
||||||
| Заметка | Aliases | Status | Type | Tags |
|
| Заметка | Aliases | Status | Type | Tags |
|
||||||
|---------|---------|--------|------|------|
|
|---------|---------|--------|------|------|
|
||||||
| [[AGENTS]] | Агент — контекст и инструкции для этого хранилища | — | — | [] |
|
| [[AGENTS]] | Агент — контекст и инструкции для этого хранилища | stable | guide | [agent, vault] |
|
||||||
| [[CLAUDE]] | CLAUDE | — | — | [] |
|
| [[CLAUDE]] | CLAUDE | — | — | [] |
|
||||||
| [[CODEX]] | CODEX | — | — | [] |
|
| [[CODEX]] | CODEX | — | — | [] |
|
||||||
| [[GEMINI]] | GEMINI | — | — | [] |
|
| [[GEMINI]] | GEMINI | — | — | [] |
|
||||||
@@ -205,7 +205,7 @@ total: 514
|
|||||||
|
|
||||||
| Заметка | Aliases | Status | Type | Tags |
|
| Заметка | Aliases | Status | Type | Tags |
|
||||||
|---------|---------|--------|------|------|
|
|---------|---------|--------|------|------|
|
||||||
| [[CI-CD — основы]] | Основы CI/CD, Непрерывная интеграция и доставка | seed | concept | [devops, cicd, gitlab-ci, jenkins] |
|
| [[CI-CD — основы]] | Основы CI/CD, Непрерывная интеграция и доставка | processing | concept | [devops, cicd, gitlab-ci, jenkins] |
|
||||||
| [[DevOps - основы]] | DevOps - основы | stable | concept | [devops] |
|
| [[DevOps - основы]] | DevOps - основы | stable | concept | [devops] |
|
||||||
| [[Git]] | Git | seed | concept | [devops, git, vcs] |
|
| [[Git]] | Git | seed | concept | [devops, git, vcs] |
|
||||||
| [[Gitlab]] | Gitlab | seed | concept | [devops, git, cicd, gitlab] |
|
| [[Gitlab]] | Gitlab | seed | concept | [devops, git, cicd, gitlab] |
|
||||||
@@ -471,16 +471,19 @@ total: 514
|
|||||||
|
|
||||||
| Заметка | Aliases | Status | Type | Tags |
|
| Заметка | Aliases | Status | Type | Tags |
|
||||||
|---------|---------|--------|------|------|
|
|---------|---------|--------|------|------|
|
||||||
|
| [[Defense-in-depth]] | DiD, Глубокоэшелонированная защита | seed | concept | [networking, security, infosec, defense-in-depth] |
|
||||||
| [[Ethernet]] | Ethernet | stable | concept | [network, networking, ethernet] |
|
| [[Ethernet]] | Ethernet | stable | concept | [network, networking, ethernet] |
|
||||||
| [[LAN vs WAN]] | LAN Vs WAN | stable | concept | [network, networking] |
|
| [[LAN vs WAN]] | LAN Vs WAN | stable | concept | [network, networking] |
|
||||||
| [[MAC-адрес]] | MAC-адрес | stable | concept | [networking] |
|
| [[MAC-адрес]] | MAC-адрес | stable | concept | [networking] |
|
||||||
| [[NAT]] | NAT | processing | concept | [network, networking, nat] |
|
| [[NAT]] | NAT | processing | concept | [network, networking, nat] |
|
||||||
| [[Networking - MOC]] | Networking - MOC | stable | moc | [network, networking] |
|
| [[Networking - MOC]] | Networking - MOC | stable | moc | [network, networking] |
|
||||||
| [[OAuth 2.0]] | OAuth 2, OAuth 2.0 | processing | concept | [web, security, oauth, authorization] |
|
| [[OAuth 2.0]] | OAuth 2, OAuth 2.0 | processing | concept | [web, security, oauth, authorization] |
|
||||||
|
| [[SASE]] | Secure Access Service Edge | seed | concept | [networking, security, infosec, sase] |
|
||||||
| [[VLSM]] | VLSM | stable | concept | [network, networking] |
|
| [[VLSM]] | VLSM | stable | concept | [network, networking] |
|
||||||
| [[VPN]] | VPN | stable | concept | [network, networking, vpn] |
|
| [[VPN]] | VPN | stable | concept | [network, networking, vpn] |
|
||||||
|
| [[Zero Trust Network Access]] | ZTNA | seed | concept | [networking, security, infosec, ztna] |
|
||||||
| [[Базовая настройка коммутатора]] | Базовая Настройка Коммутатора | stable | concept | [network, networking] |
|
| [[Базовая настройка коммутатора]] | Базовая Настройка Коммутатора | stable | concept | [network, networking] |
|
||||||
| [[Введение в сетевую безопасность]] | Введение в сетевую безопасность | seed | concept | [networking, security, infosec] |
|
| [[Введение в сетевую безопасность]] | Введение в сетевую безопасность | processing | concept | [networking, security, infosec, vulnerability-scanning] |
|
||||||
| [[Веб-аутентификация и авторизация]] | Идентификация, аутентификация и авторизация, Веб-аутентификация | processing | concept | [web, security, authentication, authorization, oauth] |
|
| [[Веб-аутентификация и авторизация]] | Идентификация, аутентификация и авторизация, Веб-аутентификация | processing | concept | [web, security, authentication, authorization, oauth] |
|
||||||
| [[Витая пара]] | Витая Пара | stable | concept | [network, networking] |
|
| [[Витая пара]] | Витая Пара | stable | concept | [network, networking] |
|
||||||
| [[Иерархическая модель Cisco]] | Иерархическая Модель Cisco | stable | concept | [network, networking] |
|
| [[Иерархическая модель Cisco]] | Иерархическая Модель Cisco | stable | concept | [network, networking] |
|
||||||
@@ -612,6 +615,7 @@ total: 514
|
|||||||
|---------|---------|--------|------|------|
|
|---------|---------|--------|------|------|
|
||||||
| [[31, 32 - Налоги]] | 31, 32 - Налоги | seed | knowledge | [study, taxes] |
|
| [[31, 32 - Налоги]] | 31, 32 - Налоги | seed | knowledge | [study, taxes] |
|
||||||
| [[31, 32 - Налоги 1]] | 31, 32 - Налоги | seed | knowledge | [study, taxes] |
|
| [[31, 32 - Налоги 1]] | 31, 32 - Налоги | seed | knowledge | [study, taxes] |
|
||||||
|
| [[CI-CD Конвейер для сборки и доставки продукта. Построение пайплайнов]] | — | seed | concept | [] |
|
||||||
| [[CIСD Конвейер для сборки и доставки продукта. Знакомство с GitlabCI и Jenkins]] | — | seed | concept | [] |
|
| [[CIСD Конвейер для сборки и доставки продукта. Знакомство с GitlabCI и Jenkins]] | — | seed | concept | [] |
|
||||||
| [[gemini-code-1780921978295]] | — | — | — | [] |
|
| [[gemini-code-1780921978295]] | — | — | — | [] |
|
||||||
| [[Git]] | Git | seed | concept | [] |
|
| [[Git]] | Git | seed | concept | [] |
|
||||||
@@ -632,6 +636,7 @@ total: 514
|
|||||||
| [[Алгоритм обратного распространения ошибки для произвольного числа слоев НС]] | Алгоритм обратного распространения ошибки для произвольного числа слоев НС | seed | concept | [] |
|
| [[Алгоритм обратного распространения ошибки для произвольного числа слоев НС]] | Алгоритм обратного распространения ошибки для произвольного числа слоев НС | seed | concept | [] |
|
||||||
| [[Алгоритм обучения сети Кохонена]] | Алгоритм обучения сети Кохонена | seed | concept | [] |
|
| [[Алгоритм обучения сети Кохонена]] | Алгоритм обучения сети Кохонена | seed | concept | [] |
|
||||||
| [[Анализ типа фин. устойчивости]] | Анализ типа фин. устойчивости | seed | concept | [] |
|
| [[Анализ типа фин. устойчивости]] | Анализ типа фин. устойчивости | seed | concept | [] |
|
||||||
|
| [[Безопасность инфраструктуры. ZTNA, SASE, DiD]] | — | seed | concept | [] |
|
||||||
| [[В случае ошибок на принтере Kyocera FS-1020MFP]] | В случае ошибок на принтере Kyocera FS-1020MFP | seed | concept | [] |
|
| [[В случае ошибок на принтере Kyocera FS-1020MFP]] | В случае ошибок на принтере Kyocera FS-1020MFP | seed | concept | [] |
|
||||||
| [[Введение в сетевую безопасность]] | Введение в сетевую безопасность | processing | concept | [] |
|
| [[Введение в сетевую безопасность]] | Введение в сетевую безопасность | processing | concept | [] |
|
||||||
| [[ВДО no кредитам]] | — | seed | concept | [] |
|
| [[ВДО no кредитам]] | — | seed | concept | [] |
|
||||||
@@ -829,3 +834,9 @@ total: 514
|
|||||||
| [[Daily Note]] | — | — | daily | [daily] |
|
| [[Daily Note]] | — | — | daily | [daily] |
|
||||||
| [[Study Item]] | — | seed | knowledge | [] |
|
| [[Study Item]] | — | seed | knowledge | [] |
|
||||||
| [[Travel Item]] | — | seed | knowledge | [travel] |
|
| [[Travel Item]] | — | seed | knowledge | [travel] |
|
||||||
|
|
||||||
|
## 99 System/Tools/agent-tools
|
||||||
|
|
||||||
|
| Заметка | Aliases | Status | Type | Tags |
|
||||||
|
|---------|---------|--------|------|------|
|
||||||
|
| [[AGENT_TOOLS]] | — | stable | guide | [agent, tooling, vault] |
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Agent tools для vault
|
||||||
|
status: stable
|
||||||
|
type: guide
|
||||||
|
tags: [agent, tooling, vault]
|
||||||
|
created: 2026-06-16
|
||||||
|
updated: 2026-06-16
|
||||||
|
aliases: []
|
||||||
|
---
|
||||||
|
|
||||||
|
# Agent tools для vault
|
||||||
|
|
||||||
|
Набор утилит для ИИ-агентов. Вывод — JSON, чтобы агент мог читать результат без парсинга Markdown-таблиц.
|
||||||
|
|
||||||
|
Запускать из корня vault:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 "99 System/Tools/agent-tools/vault_index.py" dump
|
||||||
|
```
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
### `vault_index.py`
|
||||||
|
|
||||||
|
- `dump` — вывести все заметки из vault как JSON.
|
||||||
|
- `query --term "CI/CD" --limit 10` — найти близкие заметки.
|
||||||
|
- `rebuild` — пересобрать `99 System/INDEX.md`. Это write-команда.
|
||||||
|
|
||||||
|
### `vault_ingest.py`
|
||||||
|
|
||||||
|
- `scan` — показать файлы из `00 Inbox/` и `01 Ideas/`.
|
||||||
|
- `classify PATH` — эвристически классифицировать источник.
|
||||||
|
- `plan PATH` — выдать JSON-план ingest: назначение, похожие заметки, возможные wikilinks.
|
||||||
|
|
||||||
|
Команды read-only. Они не заменяют обязательный план и подтверждение перед workflow.
|
||||||
|
|
||||||
|
### `vault_audit.py`
|
||||||
|
|
||||||
|
- `broken-links` — найти wikilinks на несуществующие заметки.
|
||||||
|
- `orphans` — найти заметки без входящих ссылок.
|
||||||
|
- `source-links` — проверить wikilinks в `source`.
|
||||||
|
- `components` — найти компоненты графа ссылок.
|
||||||
|
- `all` — выполнить все проверки.
|
||||||
|
|
||||||
|
Команды read-only.
|
||||||
|
|
||||||
|
### `vault_frontmatter.py`
|
||||||
|
|
||||||
|
- `check [PATH ...]` — проверить обязательные поля и допустимые `status/type`.
|
||||||
|
- `normalize PATH ...` — нормализовать frontmatter выбранных файлов. Это write-команда.
|
||||||
|
|
||||||
|
## Правила для агентов
|
||||||
|
|
||||||
|
- Перед `ingest`, `wiki`, `audit`, `remind`, `query` сначала читать `99 System/INDEX.md`; скрипты помогают, но не отменяют правила `AGENTS.md`.
|
||||||
|
- Write-команды запускать только когда workflow уже подтверждён пользователем или правило явно требует обновить индекс.
|
||||||
|
- `vault_ingest.py plan` — вспомогательный план, не окончательное решение. Агент обязан проверить содержимое источника сам.
|
||||||
|
- После создания, удаления, перемещения заметки или изменения frontmatter запускать `vault_index.py rebuild`.
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
|
from vault_common import all_records, json_print, known_note_names, source_values, wikilinks
|
||||||
|
|
||||||
|
|
||||||
|
def broken_links(records: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||||
|
known = known_note_names(records)
|
||||||
|
result = []
|
||||||
|
for record in records:
|
||||||
|
links = wikilinks(record["body"])
|
||||||
|
missing = sorted({link for link in links if link not in known})
|
||||||
|
if missing:
|
||||||
|
result.append({"path": record["path"], "missing": missing})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def orphans(records: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||||
|
incoming: Counter[str] = Counter()
|
||||||
|
aliases: dict[str, str] = {}
|
||||||
|
path_by_name: dict[str, str] = {}
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
path_by_name[record["name"]] = record["path"]
|
||||||
|
aliases[record["name"]] = record["name"]
|
||||||
|
aliases[record["path"]] = record["name"]
|
||||||
|
for alias in record["aliases"]:
|
||||||
|
aliases[alias] = record["name"]
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
for link in wikilinks(record["body"]):
|
||||||
|
target = aliases.get(link)
|
||||||
|
if target and target != record["name"]:
|
||||||
|
incoming[target] += 1
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for name, path in sorted(path_by_name.items(), key=lambda item: item[1]):
|
||||||
|
if incoming[name] == 0 and not path.startswith("99 System/Archive/"):
|
||||||
|
result.append({"path": path, "name": name, "incoming": 0})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def source_links(records: list[dict[str, object]]) -> dict[str, object]:
|
||||||
|
known = known_note_names(records)
|
||||||
|
linked = []
|
||||||
|
missing = []
|
||||||
|
plain = []
|
||||||
|
|
||||||
|
for record in records:
|
||||||
|
for source in source_values(record["frontmatter"].get("source")):
|
||||||
|
links = wikilinks(source)
|
||||||
|
if links:
|
||||||
|
for link in links:
|
||||||
|
item = {"path": record["path"], "source": source, "target": link}
|
||||||
|
if link in known:
|
||||||
|
linked.append(item)
|
||||||
|
else:
|
||||||
|
missing.append(item)
|
||||||
|
else:
|
||||||
|
plain.append({"path": record["path"], "source": source})
|
||||||
|
|
||||||
|
return {"linked": linked, "missing": missing, "plain": plain}
|
||||||
|
|
||||||
|
|
||||||
|
def graph_components(records: list[dict[str, object]]) -> list[dict[str, object]]:
|
||||||
|
known = known_note_names(records)
|
||||||
|
name_by_link = {}
|
||||||
|
for record in records:
|
||||||
|
name_by_link[record["name"]] = record["name"]
|
||||||
|
name_by_link[record["path"]] = record["name"]
|
||||||
|
for alias in record["aliases"]:
|
||||||
|
name_by_link[alias] = record["name"]
|
||||||
|
|
||||||
|
graph: dict[str, set[str]] = defaultdict(set)
|
||||||
|
path_by_name = {record["name"]: record["path"] for record in records}
|
||||||
|
for record in records:
|
||||||
|
name = record["name"]
|
||||||
|
graph.setdefault(name, set())
|
||||||
|
for link in wikilinks(record["body"]):
|
||||||
|
if link not in known:
|
||||||
|
continue
|
||||||
|
target = name_by_link.get(link)
|
||||||
|
if target and target != name:
|
||||||
|
graph[name].add(target)
|
||||||
|
graph[target].add(name)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
components = []
|
||||||
|
for name in sorted(graph):
|
||||||
|
if name in seen:
|
||||||
|
continue
|
||||||
|
stack = [name]
|
||||||
|
component = []
|
||||||
|
seen.add(name)
|
||||||
|
while stack:
|
||||||
|
current = stack.pop()
|
||||||
|
component.append(current)
|
||||||
|
for next_name in graph[current]:
|
||||||
|
if next_name not in seen:
|
||||||
|
seen.add(next_name)
|
||||||
|
stack.append(next_name)
|
||||||
|
components.append(
|
||||||
|
{
|
||||||
|
"size": len(component),
|
||||||
|
"notes": [path_by_name[item] for item in sorted(component)[:20]],
|
||||||
|
"truncated": len(component) > 20,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return sorted(components, key=lambda item: item["size"])
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Agent-facing vault audit helper")
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
sub.add_parser("broken-links")
|
||||||
|
sub.add_parser("orphans")
|
||||||
|
sub.add_parser("source-links")
|
||||||
|
sub.add_parser("components")
|
||||||
|
sub.add_parser("all")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
records = all_records()
|
||||||
|
if args.command == "broken-links":
|
||||||
|
json_print({"ok": True, "broken_links": broken_links(records)})
|
||||||
|
elif args.command == "orphans":
|
||||||
|
json_print({"ok": True, "orphans": orphans(records)})
|
||||||
|
elif args.command == "source-links":
|
||||||
|
json_print({"ok": True, "source_links": source_links(records)})
|
||||||
|
elif args.command == "components":
|
||||||
|
json_print({"ok": True, "components": graph_components(records)})
|
||||||
|
elif args.command == "all":
|
||||||
|
json_print(
|
||||||
|
{
|
||||||
|
"ok": True,
|
||||||
|
"broken_links": broken_links(records),
|
||||||
|
"orphans": orphans(records),
|
||||||
|
"source_links": source_links(records),
|
||||||
|
"components": graph_components(records),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from datetime import date, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
INDEX_PATH = ROOT / "99 System" / "INDEX.md"
|
||||||
|
EXCLUDED_DIRS = {".git", ".trash", ".obsidian", ".smart-env"}
|
||||||
|
REQUIRED_FRONTMATTER = {"title", "status", "type", "tags", "created", "updated"}
|
||||||
|
VALID_STATUS = {"seed", "processing", "stable"}
|
||||||
|
VALID_TYPE = {"concept", "moc", "lab", "knowledge", "guide"}
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except Exception: # pragma: no cover - depends on agent environment
|
||||||
|
yaml = None
|
||||||
|
|
||||||
|
|
||||||
|
def rel(path: Path) -> str:
|
||||||
|
return path.relative_to(ROOT).as_posix()
|
||||||
|
|
||||||
|
|
||||||
|
def json_print(data: Any) -> None:
|
||||||
|
print(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def note_files(include_index: bool = False) -> list[Path]:
|
||||||
|
files: list[Path] = []
|
||||||
|
for path in ROOT.rglob("*.md"):
|
||||||
|
parts = set(path.relative_to(ROOT).parts)
|
||||||
|
if parts & EXCLUDED_DIRS:
|
||||||
|
continue
|
||||||
|
if not include_index and path == INDEX_PATH:
|
||||||
|
continue
|
||||||
|
files.append(path)
|
||||||
|
return sorted(files, key=lambda p: rel(p).lower())
|
||||||
|
|
||||||
|
|
||||||
|
def split_frontmatter(text: str) -> tuple[dict[str, Any], str, str | None]:
|
||||||
|
lines = text.splitlines()
|
||||||
|
if not lines or lines[0].strip() != "---":
|
||||||
|
return {}, text, None
|
||||||
|
|
||||||
|
end = None
|
||||||
|
for index, line in enumerate(lines[1:], start=1):
|
||||||
|
if line.strip() == "---":
|
||||||
|
end = index
|
||||||
|
break
|
||||||
|
|
||||||
|
if end is None:
|
||||||
|
return {}, text, None
|
||||||
|
|
||||||
|
raw = "\n".join(lines[1:end])
|
||||||
|
body = "\n".join(lines[end + 1 :])
|
||||||
|
return parse_frontmatter(raw), body, raw
|
||||||
|
|
||||||
|
|
||||||
|
def parse_frontmatter(raw: str) -> dict[str, Any]:
|
||||||
|
if yaml is not None:
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(raw) or {}
|
||||||
|
return data if isinstance(data, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
data: dict[str, Any] = {}
|
||||||
|
current_key: str | None = None
|
||||||
|
|
||||||
|
for line in raw.splitlines():
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
|
||||||
|
list_item = re.match(r"^\s*-\s+(.*)$", line)
|
||||||
|
if list_item and current_key:
|
||||||
|
data.setdefault(current_key, [])
|
||||||
|
if isinstance(data[current_key], list):
|
||||||
|
data[current_key].append(clean_scalar(list_item.group(1)))
|
||||||
|
continue
|
||||||
|
|
||||||
|
pair = re.match(r"^([A-Za-zА-Яа-я0-9_-]+):\s*(.*)$", line)
|
||||||
|
if not pair:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, value = pair.group(1), pair.group(2).strip()
|
||||||
|
current_key = key
|
||||||
|
if value == "":
|
||||||
|
data[key] = [] if key in {"aliases", "tags"} else ""
|
||||||
|
elif value.startswith("[") and value.endswith("]"):
|
||||||
|
data[key] = parse_inline_list(value)
|
||||||
|
else:
|
||||||
|
data[key] = clean_scalar(value)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def parse_inline_list(value: str) -> list[str]:
|
||||||
|
inner = value.strip()[1:-1].strip()
|
||||||
|
if not inner:
|
||||||
|
return []
|
||||||
|
return [clean_scalar(item.strip()) for item in inner.split(",") if item.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def clean_scalar(value: Any) -> str:
|
||||||
|
text = str(value).strip()
|
||||||
|
if len(text) >= 2 and text[0] == text[-1] and text[0] in {"'", '"'}:
|
||||||
|
return text[1:-1]
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(value: Any) -> list[str]:
|
||||||
|
if value is None or value == "":
|
||||||
|
return []
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [clean_scalar(item) for item in value if clean_scalar(item)]
|
||||||
|
return [clean_scalar(value)]
|
||||||
|
|
||||||
|
|
||||||
|
def as_cell(value: Any, default: str = "—") -> str:
|
||||||
|
if value is None or value == "":
|
||||||
|
return default
|
||||||
|
if isinstance(value, (date, datetime)):
|
||||||
|
return value.isoformat()
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def tags_cell(value: Any) -> str:
|
||||||
|
tags = as_list(value)
|
||||||
|
return "[" + ", ".join(tags) + "]"
|
||||||
|
|
||||||
|
|
||||||
|
def note_record(path: Path) -> dict[str, Any]:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
fm, body, raw_fm = split_frontmatter(text)
|
||||||
|
return {
|
||||||
|
"path": rel(path),
|
||||||
|
"name": path.stem,
|
||||||
|
"title": as_cell(fm.get("title"), ""),
|
||||||
|
"aliases": as_list(fm.get("aliases")),
|
||||||
|
"status": as_cell(fm.get("status")),
|
||||||
|
"type": as_cell(fm.get("type")),
|
||||||
|
"tags": as_list(fm.get("tags")),
|
||||||
|
"created": as_cell(fm.get("created"), ""),
|
||||||
|
"updated": as_cell(fm.get("updated"), ""),
|
||||||
|
"frontmatter": fm,
|
||||||
|
"has_frontmatter": raw_fm is not None,
|
||||||
|
"body": body,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def all_records() -> list[dict[str, Any]]:
|
||||||
|
return [note_record(path) for path in note_files()]
|
||||||
|
|
||||||
|
|
||||||
|
def wikilinks(text: str) -> list[str]:
|
||||||
|
text = strip_markdown_code(text)
|
||||||
|
links = []
|
||||||
|
for match in re.finditer(r"\[\[([^\]]+)\]\]", text):
|
||||||
|
target = match.group(1).split("|", 1)[0].split("#", 1)[0].strip()
|
||||||
|
if target:
|
||||||
|
links.append(target)
|
||||||
|
return links
|
||||||
|
|
||||||
|
|
||||||
|
def strip_markdown_code(text: str) -> str:
|
||||||
|
text = re.sub(r"```.*?```", "", text, flags=re.DOTALL)
|
||||||
|
text = re.sub(r"`[^`\n]*`", "", text)
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def known_note_names(records: list[dict[str, Any]]) -> set[str]:
|
||||||
|
names: set[str] = set()
|
||||||
|
for record in records:
|
||||||
|
names.add(record["name"])
|
||||||
|
names.add(record["path"])
|
||||||
|
names.update(record["aliases"])
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def tokenize(value: str) -> set[str]:
|
||||||
|
return {token.lower() for token in re.findall(r"[A-Za-zА-Яа-я0-9_+-]+", value)}
|
||||||
|
|
||||||
|
|
||||||
|
def search_records(records: list[dict[str, Any]], term: str, limit: int = 10) -> list[dict[str, Any]]:
|
||||||
|
query = tokenize(term)
|
||||||
|
if not query:
|
||||||
|
return []
|
||||||
|
|
||||||
|
scored = []
|
||||||
|
for record in records:
|
||||||
|
haystack = " ".join(
|
||||||
|
[
|
||||||
|
record["name"],
|
||||||
|
record["title"],
|
||||||
|
" ".join(record["aliases"]),
|
||||||
|
" ".join(record["tags"]),
|
||||||
|
record["path"],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
tokens = tokenize(haystack)
|
||||||
|
score = len(query & tokens)
|
||||||
|
if term.lower() in haystack.lower():
|
||||||
|
score += 3
|
||||||
|
if score:
|
||||||
|
scored.append((score, record))
|
||||||
|
|
||||||
|
scored.sort(key=lambda item: (-item[0], item[1]["path"]))
|
||||||
|
return [compact_record(record) | {"score": score} for score, record in scored[:limit]]
|
||||||
|
|
||||||
|
|
||||||
|
def compact_record(record: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"path": record["path"],
|
||||||
|
"name": record["name"],
|
||||||
|
"title": record["title"],
|
||||||
|
"aliases": record["aliases"],
|
||||||
|
"status": record["status"],
|
||||||
|
"type": record["type"],
|
||||||
|
"tags": record["tags"],
|
||||||
|
"created": record["created"],
|
||||||
|
"updated": record["updated"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def write_index(records: list[dict[str, Any]]) -> None:
|
||||||
|
by_dir: dict[str, list[dict[str, Any]]] = {}
|
||||||
|
for record in records:
|
||||||
|
directory = str(Path(record["path"]).parent)
|
||||||
|
if directory == ".":
|
||||||
|
directory = "."
|
||||||
|
by_dir.setdefault(directory, []).append(record)
|
||||||
|
|
||||||
|
total = len(records)
|
||||||
|
lines = [
|
||||||
|
"---",
|
||||||
|
f"generated: {date.today().isoformat()}",
|
||||||
|
f"total: {total}",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Vault Index",
|
||||||
|
"",
|
||||||
|
"## Статистика",
|
||||||
|
"",
|
||||||
|
"| Параметр | Значение |",
|
||||||
|
"|----------|----------|",
|
||||||
|
f"| Всего заметок | {total} |",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
for directory in sorted(by_dir, key=str.lower):
|
||||||
|
lines.extend(
|
||||||
|
[
|
||||||
|
f"## {directory}",
|
||||||
|
"",
|
||||||
|
"| Заметка | Aliases | Status | Type | Tags |",
|
||||||
|
"|---------|---------|--------|------|------|",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for record in sorted(by_dir[directory], key=lambda item: item["name"].lower()):
|
||||||
|
aliases = ", ".join(record["aliases"]) if record["aliases"] else "—"
|
||||||
|
status = record["status"] or "—"
|
||||||
|
note_type = record["type"] or "—"
|
||||||
|
tags = "[" + ", ".join(record["tags"]) + "]"
|
||||||
|
lines.append(f"| [[{record['name']}]] | {aliases} | {status} | {note_type} | {tags} |")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
INDEX_PATH.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def source_values(value: Any) -> list[str]:
|
||||||
|
if isinstance(value, list):
|
||||||
|
return [clean_scalar(item) for item in value if clean_scalar(item)]
|
||||||
|
if value:
|
||||||
|
return [clean_scalar(value)]
|
||||||
|
return []
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from vault_common import (
|
||||||
|
REQUIRED_FRONTMATTER,
|
||||||
|
ROOT,
|
||||||
|
VALID_STATUS,
|
||||||
|
VALID_TYPE,
|
||||||
|
all_records,
|
||||||
|
as_list,
|
||||||
|
json_print,
|
||||||
|
note_record,
|
||||||
|
note_files,
|
||||||
|
rel,
|
||||||
|
split_frontmatter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_paths(paths: list[str]) -> list[Path]:
|
||||||
|
if not paths:
|
||||||
|
return note_files()
|
||||||
|
result = []
|
||||||
|
for item in paths:
|
||||||
|
path = Path(item)
|
||||||
|
if not path.is_absolute():
|
||||||
|
path = ROOT / path
|
||||||
|
result.append(path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def validate_record(record: dict[str, object]) -> list[str]:
|
||||||
|
fm = record["frontmatter"]
|
||||||
|
issues = []
|
||||||
|
|
||||||
|
if not record["has_frontmatter"]:
|
||||||
|
issues.append("missing_frontmatter")
|
||||||
|
|
||||||
|
for field in sorted(REQUIRED_FRONTMATTER):
|
||||||
|
if field not in fm or fm[field] in (None, ""):
|
||||||
|
issues.append(f"missing_{field}")
|
||||||
|
|
||||||
|
if fm.get("status") and str(fm["status"]) not in VALID_STATUS:
|
||||||
|
issues.append("invalid_status")
|
||||||
|
if fm.get("type") and str(fm["type"]) not in VALID_TYPE:
|
||||||
|
issues.append("invalid_type")
|
||||||
|
if "tags" in fm and not isinstance(fm["tags"], list):
|
||||||
|
issues.append("tags_not_list")
|
||||||
|
if "aliases" in fm and fm["aliases"] not in (None, "") and not isinstance(fm["aliases"], list):
|
||||||
|
issues.append("aliases_not_list")
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
|
||||||
|
def check(paths: list[Path]) -> list[dict[str, object]]:
|
||||||
|
results = []
|
||||||
|
for path in paths:
|
||||||
|
record = note_record(path)
|
||||||
|
issues = validate_record(record)
|
||||||
|
if issues:
|
||||||
|
results.append({"path": rel(path), "issues": issues})
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def yaml_list(values: list[str]) -> list[str]:
|
||||||
|
if not values:
|
||||||
|
return ["[]"]
|
||||||
|
return [""] + [f" - {value}" for value in values]
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(path: Path) -> dict[str, object]:
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
fm, body, _ = split_frontmatter(text)
|
||||||
|
title = fm.get("title") or path.stem
|
||||||
|
status = fm.get("status") if fm.get("status") in VALID_STATUS else "seed"
|
||||||
|
note_type = fm.get("type") if fm.get("type") in VALID_TYPE else "concept"
|
||||||
|
tags = as_list(fm.get("tags"))
|
||||||
|
aliases = as_list(fm.get("aliases"))
|
||||||
|
created = fm.get("created") or date.today().isoformat()
|
||||||
|
updated = date.today().isoformat()
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
"---",
|
||||||
|
f"title: {title}",
|
||||||
|
f"status: {status}",
|
||||||
|
f"type: {note_type}",
|
||||||
|
"tags:" + ("\n".join(yaml_list(tags)) if tags else " []"),
|
||||||
|
f"created: {created}",
|
||||||
|
f"updated: {updated}",
|
||||||
|
"aliases:" + ("\n".join(yaml_list(aliases)) if aliases else " []"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for key, value in fm.items():
|
||||||
|
if key in {"title", "status", "type", "tags", "created", "updated", "aliases"}:
|
||||||
|
continue
|
||||||
|
if isinstance(value, list):
|
||||||
|
lines.append(f"{key}:")
|
||||||
|
lines.extend(f" - {item}" for item in value)
|
||||||
|
elif value in (None, ""):
|
||||||
|
lines.append(f"{key}:")
|
||||||
|
else:
|
||||||
|
lines.append(f"{key}: {value}")
|
||||||
|
|
||||||
|
lines.extend(["---", "", body.lstrip("\n")])
|
||||||
|
path.write_text("\n".join(lines), encoding="utf-8")
|
||||||
|
return {"path": rel(path), "written": True}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Agent-facing frontmatter helper")
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
check_cmd = sub.add_parser("check")
|
||||||
|
check_cmd.add_argument("paths", nargs="*")
|
||||||
|
|
||||||
|
normalize_cmd = sub.add_parser("normalize")
|
||||||
|
normalize_cmd.add_argument("paths", nargs="+")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "check":
|
||||||
|
issues = check(resolve_paths(args.paths))
|
||||||
|
json_print({"ok": True, "issues": issues, "total_with_issues": len(issues)})
|
||||||
|
elif args.command == "normalize":
|
||||||
|
written = [normalize(path) for path in resolve_paths(args.paths)]
|
||||||
|
json_print({"ok": True, "written": written})
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from vault_common import all_records, compact_record, json_print, search_records, write_index
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Agent-facing vault index helper")
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
query = sub.add_parser("query", help="Search notes by term")
|
||||||
|
query.add_argument("--term", required=True)
|
||||||
|
query.add_argument("--limit", type=int, default=10)
|
||||||
|
|
||||||
|
sub.add_parser("dump", help="Dump note records as JSON")
|
||||||
|
sub.add_parser("rebuild", help="Rebuild 99 System/INDEX.md")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
records = all_records()
|
||||||
|
|
||||||
|
if args.command == "query":
|
||||||
|
json_print({"ok": True, "results": search_records(records, args.term, args.limit)})
|
||||||
|
elif args.command == "dump":
|
||||||
|
json_print({"ok": True, "total": len(records), "notes": [compact_record(r) for r in records]})
|
||||||
|
elif args.command == "rebuild":
|
||||||
|
write_index(records)
|
||||||
|
json_print({"ok": True, "written": "99 System/INDEX.md", "total": len(records)})
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from vault_common import ROOT, all_records, json_print, rel, search_records, split_frontmatter, tokenize
|
||||||
|
|
||||||
|
INBOX_DIRS = [ROOT / "00 Inbox", ROOT / "01 Ideas"]
|
||||||
|
LIBRARY_HINTS = {
|
||||||
|
"ansible": "90 Library/DevOps/Ansible",
|
||||||
|
"ci": "90 Library/DevOps",
|
||||||
|
"cd": "90 Library/DevOps",
|
||||||
|
"cicd": "90 Library/DevOps",
|
||||||
|
"docker": "90 Library/Containers/Docker",
|
||||||
|
"git": "90 Library/DevOps",
|
||||||
|
"gitlab": "90 Library/DevOps",
|
||||||
|
"jenkins": "90 Library/DevOps",
|
||||||
|
"kubernetes": "90 Library/Containers/Kubernetes",
|
||||||
|
"linux": "90 Library/Linux",
|
||||||
|
"nginx": "90 Library/HomeLab/Proxy",
|
||||||
|
"rails": "90 Library/Programming/Ruby On Rails",
|
||||||
|
"ruby": "90 Library/Programming/Ruby",
|
||||||
|
"terraform": "90 Library/DevOps",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def read_path(path_arg: str) -> Path:
|
||||||
|
path = Path(path_arg)
|
||||||
|
if not path.is_absolute():
|
||||||
|
path = ROOT / path
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def scan() -> list[dict[str, object]]:
|
||||||
|
items = []
|
||||||
|
for directory in INBOX_DIRS:
|
||||||
|
if not directory.exists():
|
||||||
|
continue
|
||||||
|
for path in sorted(directory.iterdir(), key=lambda item: item.name.lower()):
|
||||||
|
if path.name == ".gitkeep" or not path.is_file():
|
||||||
|
continue
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace") if path.suffix == ".md" else ""
|
||||||
|
fm, body, _ = split_frontmatter(text)
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"path": rel(path),
|
||||||
|
"folder": rel(directory),
|
||||||
|
"suffix": path.suffix,
|
||||||
|
"title": fm.get("title") or path.stem,
|
||||||
|
"tags": fm.get("tags") or [],
|
||||||
|
"bytes": path.stat().st_size,
|
||||||
|
"classification": classify_text(path, body, fm),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def classify_text(path: Path, body: str, frontmatter: dict[str, object]) -> dict[str, object]:
|
||||||
|
tags = frontmatter.get("tags") or []
|
||||||
|
if not isinstance(tags, list):
|
||||||
|
tags = [tags]
|
||||||
|
content = f"{path.stem}\n{' '.join(map(str, tags))}\n{body[:4000]}".lower()
|
||||||
|
tokens = tokenize(content)
|
||||||
|
|
||||||
|
idea_markers = {"идея", "idea", "business", "product", "стартап", "концепция", "задумка"}
|
||||||
|
if "01 Ideas" in rel(path) or tokens & idea_markers:
|
||||||
|
kind = "idea"
|
||||||
|
destination = "01 Ideas"
|
||||||
|
else:
|
||||||
|
kind = "library"
|
||||||
|
destination = "90 Library/Other"
|
||||||
|
for marker, folder in LIBRARY_HINTS.items():
|
||||||
|
if marker in content:
|
||||||
|
destination = folder
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
"kind": kind,
|
||||||
|
"destination": destination,
|
||||||
|
"confidence": "heuristic",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def plan(path: Path) -> dict[str, object]:
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
fm, body, _ = split_frontmatter(text)
|
||||||
|
classification = classify_text(path, body, fm)
|
||||||
|
records = all_records()
|
||||||
|
terms = " ".join([path.stem, str(fm.get("title") or ""), " ".join(map(str, fm.get("tags") or []))])
|
||||||
|
similar = []
|
||||||
|
for item in search_records(records, terms, limit=30):
|
||||||
|
if item["path"] == rel(path):
|
||||||
|
continue
|
||||||
|
if item["path"].startswith(("03 Journal/", "99 System/Export/", "99 System/Cache/", "99 System/Template/")):
|
||||||
|
continue
|
||||||
|
similar.append(item)
|
||||||
|
if len(similar) == 8:
|
||||||
|
break
|
||||||
|
wikilinks = [item["name"] for item in similar[:5]]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"path": rel(path),
|
||||||
|
"classification": classification,
|
||||||
|
"similar_notes": similar,
|
||||||
|
"suggested_wikilinks": wikilinks,
|
||||||
|
"write_policy": "read-only plan; agent must ask before workflow changes",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Agent-facing ingest helper")
|
||||||
|
sub = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
sub.add_parser("scan", help="List files in 00 Inbox and 01 Ideas")
|
||||||
|
classify = sub.add_parser("classify", help="Classify one source file")
|
||||||
|
classify.add_argument("path")
|
||||||
|
plan_cmd = sub.add_parser("plan", help="Build JSON ingest plan for one source file")
|
||||||
|
plan_cmd.add_argument("path")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "scan":
|
||||||
|
json_print({"ok": True, "items": scan()})
|
||||||
|
elif args.command == "classify":
|
||||||
|
path = read_path(args.path)
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
fm, body, _ = split_frontmatter(text)
|
||||||
|
json_print({"ok": True, "path": rel(path), "classification": classify_text(path, body, fm)})
|
||||||
|
elif args.command == "plan":
|
||||||
|
json_print({"ok": True, "plan": plan(read_path(args.path))})
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
---
|
---
|
||||||
created: 2026-04-20
|
created: 2026-04-20
|
||||||
updated: 2026-06-05
|
updated: 2026-06-16
|
||||||
|
title: Агент — контекст и инструкции для этого хранилища
|
||||||
|
status: stable
|
||||||
|
type: guide
|
||||||
|
tags: [agent, vault]
|
||||||
aliases: ["Агент — контекст и инструкции для этого хранилища"]
|
aliases: ["Агент — контекст и инструкции для этого хранилища"]
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,6 +90,16 @@ aliases: ["Агент — контекст и инструкции для это
|
|||||||
- **Обновлять** после создания/удаления/перемещения заметки или изменения frontmatter.
|
- **Обновлять** после создания/удаления/перемещения заметки или изменения frontmatter.
|
||||||
- **Как:** пройтись по всем `.md` (исключая `.trash`, `.obsidian`, `.git`, `.smart-env`, сам `99 System/INDEX.md`), извлечь frontmatter, перезаписать файл в том же формате (frontmatter с `generated`/`total`, таблицы по секциям).
|
- **Как:** пройтись по всем `.md` (исключая `.trash`, `.obsidian`, `.git`, `.smart-env`, сам `99 System/INDEX.md`), извлечь frontmatter, перезаписать файл в том же формате (frontmatter с `generated`/`total`, таблицы по секциям).
|
||||||
|
|
||||||
|
## Agent tools
|
||||||
|
|
||||||
|
Утилиты для ИИ-агентов: `99 System/Tools/agent-tools/`.
|
||||||
|
|
||||||
|
- Документация: `99 System/Tools/agent-tools/AGENT_TOOLS.md`.
|
||||||
|
- Вывод скриптов — JSON, чтобы агент мог использовать результат без парсинга Markdown.
|
||||||
|
- Read-only команды можно использовать для ориентации: поиск по индексу, план ingest, audit, проверка frontmatter.
|
||||||
|
- Write-команды (`vault_index.py rebuild`, `vault_frontmatter.py normalize`) запускать только после подтверждённого workflow или когда правило явно требует обновить `INDEX.md`.
|
||||||
|
- Скрипты помогают, но не заменяют чтение исходников и правила этого файла.
|
||||||
|
|
||||||
## Workflow: Ingest
|
## Workflow: Ingest
|
||||||
|
|
||||||
Триггер: `ingest` или новые файлы в `00 Inbox/`.
|
Триггер: `ingest` или новые файлы в `00 Inbox/`.
|
||||||
|
|||||||
Reference in New Issue
Block a user