@nitra/cursor 1.13.52 → 1.13.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -91,14 +91,16 @@
91
91
  * **HPA / PDB / topologySpreadConstraints:** для кожного **`Deployment`** у шарі **`…/k8s/…/base/`**
92
92
  * (будь-який `.yaml` у цьому каталозі) обов'язкові канонічні **topologySpreadConstraints**, а HPA і PDB
93
93
  * живуть у sibling каталозі **`…/k8s/…/components/`** (Kustomize Component, фіксована назва каталогу `components`). У `base/`
94
- * заборонено тримати локальні `hpa.yaml`, `networkpolicy.yaml` і `pdb.yaml` (file-existence error) і також у дереві
95
- * base-kustomize не повинно бути HPA/PDB/NetworkPolicy через `resources` / `components` / `bases`.
94
+ * заборонено тримати локальні `hpa.yaml` і `pdb.yaml` (file-existence error) і також у дереві
95
+ * base-kustomize не повинно бути HPA/PDB через `resources` / `components` / `bases`.
96
96
  * **NetworkPolicy:** для кожного **`Deployment`**, **`StatefulSet`**, **`DaemonSet`**, **`Job`**, **`CronJob`** під `k8s`
97
- * обов'язковий канонічний NetworkPolicy (base `components/networkpolicy.yaml`, інші шари `networkpolicy.yaml` поруч).
97
+ * обов'язковий канонічний NetworkPolicy у `networkpolicy.yaml` поруч з workload-маніфестом у base
98
+ * (`base/networkpolicy.yaml`, підключений через `base/kustomization.yaml` `resources:` — обмеження діють і на dev)
99
+ * і у не-base overlay (опційно — overlay-specific override).
98
100
  * Egress: kube-dns; **TCP 80/443** на `0.0.0.0/0`; інші порти — `namespaceSelector: {}` (in-cluster / `*.svc`). Заборонено `egress: [{}]`.
99
101
  * Відсутні документи **`check k8s`** створює автоматично (multi-doc у одному файлі, якщо workload-ів кілька).
100
102
  * Структура `components/`: `kustomization.yaml` з `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`,
101
- * `resources` що містять `hpa.yaml`, `networkpolicy.yaml` і `pdb.yaml`, `hpa.yaml` (валідний `autoscaling/v2`
103
+ * `resources` що містять `hpa.yaml` і `pdb.yaml`, `hpa.yaml` (валідний `autoscaling/v2`
102
104
  * HorizontalPodAutoscaler з `scaleTargetRef.name` = ім'я Deployment, dev-like `min=max=1`), `pdb.yaml` (валідний
103
105
  * `policy/v1` PodDisruptionBudget з `selector.matchLabels.app` = мітка `app` Deployment, dev-like `minAvailable=0`).
104
106
  * Overlays (`ua/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для
@@ -133,7 +135,7 @@
133
135
  * поки в наслідуваному `base` у дереві не з'явиться такий Deployment (k8s.mdc).
134
136
  */
135
137
  import { existsSync } from 'node:fs'
136
- import { mkdir, readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises'
138
+ import { readFile, readdir, stat, unlink, writeFile } from 'node:fs/promises'
137
139
  import { basename, dirname, join, relative, resolve } from 'node:path'
138
140
 
139
141
  import { isSeq, parseAllDocuments, parseDocument } from 'yaml'
@@ -1776,7 +1778,7 @@ export function baseKustomizationNamespaceViolation(obj) {
1776
1778
  }
1777
1779
 
1778
1780
  /**
1779
- * Збирає всі `*.yaml` та `*.yml` під деревом від кореня cwd, якщо шлях містить сегмент `k8s` (для `.yml` далі — помилка перейменування).
1781
+ * Збирає всі `*.yaml` та `*.yml` під деревом від кореня cwd, якщо шлях містить сегмент `k8s` (для `.yml` далі — fail з порадою перейменувати на `.yaml`; k8s.mdc).
1780
1782
  * @param {string} root корінь репозиторію (cwd)
1781
1783
  * @param {string[]} [ignorePaths] шляхи каталогів, повністю виключених з обходу
1782
1784
  * @returns {Promise<string[]>} відсортовані абсолютні шляхи до файлів
@@ -5155,6 +5157,8 @@ function validateNetworkPolicyForWorkload(npDocs, workloadName, appLabel, worklo
5155
5157
  * що дорівнює `metadata.name` цього Deployment, з dev-like значеннями `min=max=1`.
5156
5158
  * - `components/pdb.yaml` — валідний `policy/v1` `PodDisruptionBudget` зі `selector.matchLabels.app`,
5157
5159
  * що дорівнює мітці `app` Deployment, з dev-like `minAvailable=0`.
5160
+ * - **NetworkPolicy** в components не живе — він підключений з `base/networkpolicy.yaml` через
5161
+ * `base/kustomization.yaml` `resources:` (див. `validateNetworkPoliciesForK8sWorkloads`).
5158
5162
  * @param {string} baseDir абсолютний шлях до `…/k8s/…/base/`
5159
5163
  * @param {string} deployName ім'я Deployment з base
5160
5164
  * @param {string} appLabel мітка `app` з `spec.selector.matchLabels.app`
@@ -5168,7 +5172,7 @@ export async function validateComponentsForBaseDeployment(baseDir, deployName, a
5168
5172
  const componentsRel = (relative(root, componentsDir) || componentsDir).replaceAll('\\', '/')
5169
5173
  if (!existsSync(componentsDir)) {
5170
5174
  fail(
5171
- `${componentsRel}: для Deployment '${deployName}' з sibling base/ обов'язковий каталог components/ з hpa.yaml, networkpolicy.yaml і pdb.yaml (Kustomize Component) (k8s.mdc)`
5175
+ `${componentsRel}: для Deployment '${deployName}' з sibling base/ обов'язковий каталог components/ з hpa.yaml і pdb.yaml (Kustomize Component) (k8s.mdc)`
5172
5176
  )
5173
5177
  return
5174
5178
  }
@@ -5184,21 +5188,13 @@ export async function validateComponentsForBaseDeployment(baseDir, deployName, a
5184
5188
  }
5185
5189
  await validateComponentsKustomizationManifest(componentsDir, componentsRel, fail, passFn)
5186
5190
  await validateComponentsHpaFile(componentsDir, componentsRel, deployName, fail, passFn)
5187
- await validateComponentsNetworkPolicyFile(
5188
- componentsDir,
5189
- componentsRel,
5190
- deployName,
5191
- appLabel,
5192
- 'Deployment',
5193
- fail,
5194
- passFn
5195
- )
5196
5191
  await validateComponentsPdbFile(componentsDir, componentsRel, deployName, appLabel, fail, passFn)
5197
5192
  }
5198
5193
 
5199
5194
  /**
5200
5195
  * Перевіряє `components/kustomization.yaml`: `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`,
5201
- * `resources` містить `hpa.yaml`, `networkpolicy.yaml` і `pdb.yaml` (як мінімум).
5196
+ * `resources` містить `hpa.yaml` і `pdb.yaml` (як мінімум). NetworkPolicy у components вже не живе —
5197
+ * він підключений з `base/networkpolicy.yaml`.
5202
5198
  * @param {string} componentsDir абсолютний шлях до каталогу `components/`
5203
5199
  * @param {string} componentsRel відносний шлях для повідомлень
5204
5200
  * @param {(msg: string) => void} fail callback при помилці
@@ -5228,21 +5224,15 @@ async function validateComponentsKustomizationManifest(componentsDir, components
5228
5224
  }
5229
5225
  const resources = Array.isArray(obj.resources) ? obj.resources.filter(x => typeof x === 'string') : []
5230
5226
  const hasHpa = resources.includes(HPA_FILENAME)
5231
- const hasNp = resources.includes(NETWORK_POLICY_FILENAME)
5232
5227
  const hasPdb = resources.includes(PDB_FILENAME)
5233
5228
  if (!hasHpa) {
5234
5229
  fail(`${componentsRel}/kustomization.yaml: у resources має бути '${HPA_FILENAME}' (k8s.mdc)`)
5235
5230
  }
5236
- if (!hasNp) {
5237
- fail(`${componentsRel}/kustomization.yaml: у resources має бути '${NETWORK_POLICY_FILENAME}' (k8s.mdc)`)
5238
- }
5239
5231
  if (!hasPdb) {
5240
5232
  fail(`${componentsRel}/kustomization.yaml: у resources має бути '${PDB_FILENAME}' (k8s.mdc)`)
5241
5233
  }
5242
- if (obj.apiVersion === KUSTOMIZE_COMPONENT_API_VERSION && obj.kind === 'Component' && hasHpa && hasNp && hasPdb) {
5243
- passFn(
5244
- `${componentsRel}/kustomization.yaml: канонічний Kustomize Component з hpa.yaml, networkpolicy.yaml і pdb.yaml (k8s.mdc)`
5245
- )
5234
+ if (obj.apiVersion === KUSTOMIZE_COMPONENT_API_VERSION && obj.kind === 'Component' && hasHpa && hasPdb) {
5235
+ passFn(`${componentsRel}/kustomization.yaml: канонічний Kustomize Component з hpa.yaml і pdb.yaml (k8s.mdc)`)
5246
5236
  }
5247
5237
  }
5248
5238
 
@@ -5266,36 +5256,6 @@ async function validateComponentsHpaFile(componentsDir, componentsRel, deployNam
5266
5256
  validateHpaForDeployment(hpaDocs, deployName, true, hpaRel, fail, passFn)
5267
5257
  }
5268
5258
 
5269
- /**
5270
- * Перевіряє `components/networkpolicy.yaml`: NetworkPolicy для Deployment.
5271
- * @param {string} componentsDir абсолютний шлях до каталогу `components/`
5272
- * @param {string} componentsRel відносний шлях для повідомлень
5273
- * @param {string} deployName ім'я Deployment з base
5274
- * @param {string} appLabel мітка `app` Deployment
5275
- * @param {string} workloadKind вид workload для повідомлень
5276
- * @param {(msg: string) => void} fail callback при помилці
5277
- * @param {(msg: string) => void} passFn callback при успіху
5278
- * @returns {Promise<void>} результат
5279
- */
5280
- async function validateComponentsNetworkPolicyFile(
5281
- componentsDir,
5282
- componentsRel,
5283
- deployName,
5284
- appLabel,
5285
- workloadKind,
5286
- fail,
5287
- passFn
5288
- ) {
5289
- const npAbs = join(componentsDir, NETWORK_POLICY_FILENAME)
5290
- const npRel = `${componentsRel}/${NETWORK_POLICY_FILENAME}`
5291
- if (!existsSync(npAbs)) {
5292
- fail(`${npRel}: відсутній — додай NetworkPolicy для ${workloadKind} '${deployName}' (k8s.mdc)`)
5293
- return
5294
- }
5295
- const npDocs = await readAllDocsByKindFromFile(npAbs, 'NetworkPolicy')
5296
- validateNetworkPolicyForWorkload(npDocs, deployName, appLabel, workloadKind, npRel, fail, passFn)
5297
- }
5298
-
5299
5259
  /**
5300
5260
  * Перевіряє `components/pdb.yaml`: PDB для Deployment, dev-like `minAvailable=0`.
5301
5261
  * @param {string} componentsDir абсолютний шлях до каталогу `components/`
@@ -5380,7 +5340,6 @@ async function validateDeploymentsInDir(deployments, dir, root, fail, passFn) {
5380
5340
  const deployRel = relDir === '' ? '.' : relDir
5381
5341
  if (isK8sBaseLayer && deployments.length > 0) {
5382
5342
  failIfBaseLayerHasLocalHpaOrPdb(dir, deployRel, fail)
5383
- failIfBaseLayerHasLocalNetworkPolicy(dir, deployRel, fail)
5384
5343
  }
5385
5344
  const hpaDocs = isK8sBaseLayer ? [] : await readDocsByKindInDir(dir, 'HorizontalPodAutoscaler', HPA_FILENAME)
5386
5345
  const pdbDocs = isK8sBaseLayer ? [] : await readDocsByKindInDir(dir, 'PodDisruptionBudget', PDB_FILENAME)
@@ -5420,20 +5379,6 @@ function failIfBaseLayerHasLocalHpaOrPdb(dir, deployRel, fail) {
5420
5379
  }
5421
5380
  }
5422
5381
 
5423
- /**
5424
- * У шарі `…/k8s/…/base/` забороняє локальний `networkpolicy.yaml` (має жити у sibling `components/`).
5425
- * @param {string} dir абсолютний каталог Deployment-маніфесту
5426
- * @param {string} deployRel відносний шлях для повідомлень
5427
- * @param {(msg: string) => void} fail callback при порушенні
5428
- */
5429
- function failIfBaseLayerHasLocalNetworkPolicy(dir, deployRel, fail) {
5430
- if (existsSync(join(dir, NETWORK_POLICY_FILENAME))) {
5431
- fail(
5432
- `${deployRel}/${NETWORK_POLICY_FILENAME}: у шарі k8s/.../base не тримай локальний networkpolicy.yaml — NetworkPolicy живе у sibling components/ (k8s.mdc)`
5433
- )
5434
- }
5435
- }
5436
-
5437
5382
  /**
5438
5383
  * Якщо у Deployment є `metadata.name` і `spec.selector.matchLabels.app` — викликає
5439
5384
  * `validateComponentsForBaseDeployment` для звірки sibling-`components/`. Без цих ключів
@@ -5545,7 +5490,7 @@ async function validateDeploymentHpaPdbAndTopology(root, yamlFilesAbs, fail, pas
5545
5490
 
5546
5491
  /**
5547
5492
  * Перевіряє NetworkPolicy для **Deployment**, **StatefulSet**, **DaemonSet**, **Job**, **CronJob**
5548
- * під `k8s` (base `components/networkpolicy.yaml`, інші шари `networkpolicy.yaml` поруч).
5493
+ * під `k8s` у `networkpolicy.yaml` поруч з workload-маніфестом base, у не-base — як overlay-specific override).
5549
5494
  * @param {string} root корінь репозиторію
5550
5495
  * @param {string[]} yamlFilesAbs yaml під k8s
5551
5496
  * @param {(msg: string) => void} fail callback при помилці
@@ -5558,11 +5503,7 @@ async function validateNetworkPoliciesForK8sWorkloads(root, yamlFilesAbs, fail,
5558
5503
  for (const [dir, workloads] of workloadsByDir) {
5559
5504
  const relDir = (relative(rootNorm, dir) || dir).replaceAll('\\', '/')
5560
5505
  const deployRel = relDir === '' ? '.' : relDir
5561
- const isBase = isK8sYamlUnderBaseDirectory(`${relDir}/probe.yaml`)
5562
- if (isBase && workloads.length > 0) {
5563
- failIfBaseLayerHasLocalNetworkPolicy(dir, deployRel, fail)
5564
- }
5565
- const npAbs = isBase ? join(dir, '..', COMPONENTS_DIR, NETWORK_POLICY_FILENAME) : join(dir, NETWORK_POLICY_FILENAME)
5506
+ const npAbs = join(dir, NETWORK_POLICY_FILENAME)
5566
5507
  const npRel = (relative(rootNorm, npAbs) || npAbs).replaceAll('\\', '/')
5567
5508
  const npDocs = existsSync(npAbs) ? await readAllDocsByKindFromFile(npAbs, 'NetworkPolicy') : []
5568
5509
  for (const workload of workloads) {
@@ -6486,7 +6427,8 @@ export async function regenerateLegacyNetworkPolicyDocsInFile(npAbs) {
6486
6427
  }
6487
6428
 
6488
6429
  /**
6489
- * Створює відсутні NetworkPolicy для workload-ів у каталозі (base → `components/`, інакше поруч).
6430
+ * Створює відсутні NetworkPolicy для workload-ів у каталозі (`networkpolicy.yaml` поруч з workload-маніфестом).
6431
+ * Якщо каталог — base, додатково додає `networkpolicy.yaml` у `kustomization.yaml` `resources:` (якщо файл існує).
6490
6432
  * @param {string} dir абсолютний каталог workload-маніфесту
6491
6433
  * @param {Record<string, unknown>[]} workloads workload-документи з цього каталогу
6492
6434
  * @param {string} rootNorm корінь репо
@@ -6496,8 +6438,7 @@ export async function regenerateLegacyNetworkPolicyDocsInFile(npAbs) {
6496
6438
  */
6497
6439
  async function ensureNetworkPoliciesForWorkloadsInDir(dir, workloads, rootNorm, fail, passFn) {
6498
6440
  const relDir = (relative(rootNorm, dir) || dir).replaceAll('\\', '/')
6499
- const isBase = isK8sYamlUnderBaseDirectory(`${relDir}/probe.yaml`)
6500
- const npAbs = isBase ? join(dir, '..', COMPONENTS_DIR, NETWORK_POLICY_FILENAME) : join(dir, NETWORK_POLICY_FILENAME)
6441
+ const npAbs = join(dir, NETWORK_POLICY_FILENAME)
6501
6442
  const npRel = (relative(rootNorm, npAbs) || npAbs).replaceAll('\\', '/')
6502
6443
  if (existsSync(npAbs)) {
6503
6444
  const migrated = await regenerateLegacyNetworkPolicyDocsInFile(npAbs)
@@ -6519,19 +6460,15 @@ async function ensureNetworkPoliciesForWorkloadsInDir(dir, workloads, rootNorm,
6519
6460
  }
6520
6461
  if (toAdd.length === 0) return
6521
6462
  try {
6522
- if (isBase) await mkdir(dirname(npAbs), { recursive: true })
6523
6463
  await appendNetworkPolicyDocuments(npAbs, toAdd, npRel, passFn)
6524
- if (isBase) {
6525
- const componentsDir = dirname(npAbs)
6526
- const componentsRel = (relative(rootNorm, componentsDir) || componentsDir).replaceAll('\\', '/')
6527
- const kustAbs = join(componentsDir, 'kustomization.yaml')
6528
- if (existsSync(kustAbs)) {
6529
- const raw = await readFile(kustAbs, 'utf8')
6530
- const { changed, content } = ensureResourceInKustomizationYaml(raw, NETWORK_POLICY_FILENAME)
6531
- if (changed) {
6532
- await writeFile(kustAbs, content, 'utf8')
6533
- passFn(`${componentsRel}/kustomization.yaml: додано '${NETWORK_POLICY_FILENAME}' у resources (k8s.mdc)`)
6534
- }
6464
+ const kustAbs = join(dir, 'kustomization.yaml')
6465
+ if (existsSync(kustAbs)) {
6466
+ const raw = await readFile(kustAbs, 'utf8')
6467
+ const { changed, content } = ensureResourceInKustomizationYaml(raw, NETWORK_POLICY_FILENAME)
6468
+ if (changed) {
6469
+ await writeFile(kustAbs, content, 'utf8')
6470
+ const kustRel = relDir === '' ? 'kustomization.yaml' : `${relDir}/kustomization.yaml`
6471
+ passFn(`${kustRel}: додано '${NETWORK_POLICY_FILENAME}' у resources (k8s.mdc)`)
6535
6472
  }
6536
6473
  }
6537
6474
  } catch (error) {
@@ -6542,7 +6479,7 @@ async function ensureNetworkPoliciesForWorkloadsInDir(dir, workloads, rootNorm,
6542
6479
 
6543
6480
  /**
6544
6481
  * Автоматично створює відсутні **NetworkPolicy** для Deployment, StatefulSet, DaemonSet, Job і CronJob
6545
- * під `k8s` (base `components/`, інші шари → поруч).
6482
+ * під `k8s` (`networkpolicy.yaml` поруч з workload-маніфестом, у base додаток у `base/kustomization.yaml` resources).
6546
6483
  * @param {string} root корінь репозиторію
6547
6484
  * @param {string[]} yamlFilesAbs абсолютні шляхи yaml під `k8s`
6548
6485
  * @param {(msg: string) => void} fail callback при помилці
package/rules/k8s/k8s.mdc CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
3
- version: '1.39'
3
+ version: '1.41'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
@@ -33,7 +33,7 @@ alwaysApply: false
33
33
 
34
34
  **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`check-k8s.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
35
35
 
36
- **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling **`components/networkpolicy.yaml`** без `metadata.namespace`: сирий dir-скан не виконує kustomize-збірку й бачить порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, тож `podSelector` не матчиться. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
36
+ **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Збірка через kustomize нормалізує namespace на workload-маніфестах і **NetworkPolicy у `base/networkpolicy.yaml`** (через `base/kustomization.yaml` `namespace:`), що дає коректний матчинг `podSelector` у `C-0260` (`Missing network policy`) і дозволяє kubescape бачити дерево overlays / components зі справжніми ресурсами. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
37
37
 
38
38
  ### Винятки kubescape: `.kubescape-exceptions.json`
39
39
 
@@ -121,7 +121,7 @@ resources:
121
121
  memory: '128Mi'
122
122
  ```
123
123
 
124
- **HPA, PDB і NetworkPolicy у base не тримаємо**: ні локальних `hpa.yaml` / `pdb.yaml` / `networkpolicy.yaml` поруч із workload-маніфестами, ні через `resources` / `components` / `bases`. Канон — sibling каталог **`components/`** (Kustomize Component) поруч з `base/` (див. розділ нижче).
124
+ **HPA і PDB у base не тримаємо**: ні локальних `hpa.yaml` / `pdb.yaml` поруч із workload-маніфестами, ні через `resources` / `components` / `bases`. Канон — sibling каталог **`components/`** (Kustomize Component) поруч з `base/`, який підключають лише прод-overlays (див. розділ нижче). **NetworkPolicy** — навпаки: **обов'язковий і у `base/`**, у вигляді `base/networkpolicy.yaml` поруч з workload-маніфестом, підключений через `base/kustomization.yaml` `resources:` — щоб обмеження діяли і на dev-середовищі.
125
125
 
126
126
  ### Поза base (оверлеї, окремі каталоги)
127
127
 
@@ -381,33 +381,34 @@ images:
381
381
 
382
382
  **`check k8s`:** заборонено **`kind: Ingress`**.
383
383
 
384
- ## Deployment: `topologySpreadConstraints`, HPA / PDB через `components/`
384
+ ## Deployment: `topologySpreadConstraints`, HPA / PDB через `components/`, NetworkPolicy у `base/`
385
385
 
386
- Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) сам Deployment має канонічні **`spec.template.spec.topologySpreadConstraints`**, а **HPA і PDB** живуть у **sibling каталозі** **`…/k8s/…/components/`** (Kustomize Component, фіксована назва каталогу — `components`). У `base/` локальні `hpa.yaml`, `networkpolicy.yaml` і `pdb.yaml` **заборонені** (file-existence error). У дереві base-kustomize HPA / PDB / NetworkPolicy також **не дозволені** через `resources` / `components` / `bases`.
386
+ Для **кожного** `kind: Deployment` у каталозі **`…/k8s/…/base/`** (у будь-якому файлі `.yaml`, наприклад **`deploy.yaml`**, **`deployment.yaml`**) сам Deployment має канонічні **`spec.template.spec.topologySpreadConstraints`**, а **HPA і PDB** живуть у **sibling каталозі** **`…/k8s/…/components/`** (Kustomize Component, фіксована назва каталогу — `components`). У `base/` локальні `hpa.yaml` і `pdb.yaml` **заборонені** (file-existence error). У дереві base-kustomize HPA / PDB також **не дозволені** через `resources` / `components` / `bases`.
387
387
 
388
- Для **кожного** з **`Deployment`**, **`StatefulSet`**, **`DaemonSet`**, **`Job`**, **`CronJob`** під `k8s` обов'язковий **NetworkPolicy**: у **`…/k8s/…/base/`** — у **`components/networkpolicy.yaml`** (multi-doc, якщо workload-ів кілька); у **не-base** — **`networkpolicy.yaml`** поруч із маніфестом workload у тому ж каталозі. `metadata.name` NetworkPolicy **= `metadata.name`** workload; `spec.podSelector.matchLabels.app` **= мітка `app`** з `spec.selector.matchLabels` (для **CronJob** — з `spec.jobTemplate.spec.selector.matchLabels`). Відсутні документи **`check k8s`** створює автоматично.
388
+ **NetworkPolicy** — інша історія: оскільки обмеження мережі мають діяти **і на dev**, NP лежить **у `base/`** (а не в `components/`). Для **кожного** з **`Deployment`**, **`StatefulSet`**, **`DaemonSet`**, **`Job`**, **`CronJob`** під `k8s` обов'язковий **NetworkPolicy**: у **`…/k8s/…/base/`** — у **`base/networkpolicy.yaml`** поруч з workload-маніфестом (multi-doc, якщо workload-ів кілька); у **не-base** — **`networkpolicy.yaml`** поруч із маніфестом workload у тому ж каталозі (overlay-specific override). `metadata.name` NetworkPolicy **= `metadata.name`** workload; `spec.podSelector.matchLabels.app` **= мітка `app`** з `spec.selector.matchLabels` (для **CronJob** — з `spec.jobTemplate.spec.selector.matchLabels`). У `base/kustomization.yaml` `resources:` має бути `networkpolicy.yaml`. Відсутні документи **`check k8s`** створює автоматично і додає `networkpolicy.yaml` у `base/kustomization.yaml` `resources:`.
389
389
 
390
- **Канонічна структура `<pkg>/k8s/components/`** (sibling до `base/`):
390
+ **Канонічна структура `<pkg>/k8s/components/`** (sibling до `base/`) — лише HPA і PDB:
391
391
 
392
- - **`kustomization.yaml`** — `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`, `resources: [hpa.yaml, networkpolicy.yaml, pdb.yaml]` (відсортовано за алфавітом).
392
+ - **`kustomization.yaml`** — `apiVersion: kustomize.config.k8s.io/v1alpha1`, `kind: Component`, `resources: [hpa.yaml, pdb.yaml]` (відсортовано за алфавітом).
393
393
  - **`hpa.yaml`** — `autoscaling/v2`, `HorizontalPodAutoscaler`, **без** `metadata.namespace` (namespace задає kustomization-споживач), `spec.scaleTargetRef.name` **= `metadata.name`** Deployment з base, dev-like значення `minReplicas: 1`, `maxReplicas: 1`.
394
- - **`networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace`; один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у sibling `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns (UDP/TCP 53); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні, включно з metadata `169.254.169.254:80`); **in-cluster** — `to.namespaceSelector: {}` з **явним списком TCP-портів** (`80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318`; трафік на `*.svc` / Pod-и в кластері). Заборонено: `egress: [{}]`; `to.namespaceSelector: {}` без `ports:` (catch-all). Додаткові in-cluster порти можна додати вручну у `ports:` цього rule. Канон: [networkpolicy.snippet.yaml](./policy/network_policy/template/networkpolicy.snippet.yaml). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
395
394
  - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, **без** `metadata.namespace`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment, dev-like `minAvailable: 0`.
396
395
 
396
+ **Канонічний `base/networkpolicy.yaml`** — `networking.k8s.io/v1`, `NetworkPolicy`, **без** `metadata.namespace` (namespace додає `base/kustomization.yaml`); один або кілька документів (`---`) — по одному на кожен workload (**Deployment** / **StatefulSet** / **DaemonSet** / **Job** / **CronJob**) у тому ж `base/`; `metadata.name` **= `metadata.name`** workload, `spec.podSelector.matchLabels.app` **= мітка `app`** workload. **Ingress:** `from.podSelector: {}` (інші Pod у namespace). **Egress (усі workload-и):** kube-dns (UDP/TCP 53); **TCP 80 і 443** на `0.0.0.0/0` (HTTP/HTTPS назовні, включно з metadata `169.254.169.254:80`); **in-cluster** — `to.namespaceSelector: {}` з **явним списком TCP-портів** (`80, 443, 5432, 3306, 1433, 6379, 8080, 4317, 4318`; трафік на `*.svc` / Pod-и в кластері). Заборонено: `egress: [{}]`; `to.namespaceSelector: {}` без `ports:` (catch-all). Додаткові in-cluster порти можна додати вручну у `ports:` цього rule. Канон: [networkpolicy.snippet.yaml](./policy/network_policy/template/networkpolicy.snippet.yaml). Якщо документа для workload немає — **`check k8s`** дописує його автоматично.
397
+
397
398
  Інші назви каталогу (`scale/`, `hpa-component/`, `pdb-component/`) — fail.
398
399
 
399
- **Overlays** (`ua/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно.
400
+ **Overlays** (`ua/`, прод-overlays) підключають `components: [- ../components]` і додають JSON6902-патчі для прод-значень: `/spec/minReplicas`, `/spec/maxReplicas` (HPA), `/spec/minAvailable` (PDB). NetworkPolicy успадковується з base через `resources: [- ../base]` — окремий `networkpolicy.yaml` у overlay не обов'язковий і додається тільки для overlay-specific правил. Dev-середовище (`base`) HPA/PDB не отримує — як і потрібно — а NetworkPolicy діє з тим самим каноном.
400
401
 
401
402
  **`<pkg>/k8s/components/kustomization.yaml`** має `kind: Component` (не `kind: Kustomization`) — це **джерело** канонічних HPA/PDB для всіх overlays, а не overlay сам по собі. Прод-перезаписи (`/spec/minReplicas`, `/spec/maxReplicas`, `/spec/minAvailable`) живуть у `<env>/kustomization.yaml`, що підключає Component через `components:`. У самому Component patches не потрібні — він env-неутральний; **`check k8s`** не вимагає прод-патчів від `components/kustomization.yaml`.
402
403
 
403
- У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремі **`hpa.yaml`**, **`networkpolicy.yaml`** і **`pdb.yaml`**, якщо такі потрібні для цього середовища. Для **StatefulSet**, **DaemonSet**, **Job**, **CronJob** у не-base — обов'язковий лише **`networkpolicy.yaml`** (HPA/PDB лишаються прив'язаними до **Deployment**, якщо є). **`check k8s`** звіряє прив'язку за іменами (і **дописує** відсутні NetworkPolicy-документи автоматично):
404
+ У **не-base** оверлеях (без `components/`) поруч із `Deployment` лишається звична схема: окремі **`hpa.yaml`**, **`networkpolicy.yaml`** і **`pdb.yaml`**, якщо такі потрібні для цього середовища (NP overlay-specific override). Для **StatefulSet**, **DaemonSet**, **Job**, **CronJob** у не-base — обов'язкового локального `networkpolicy.yaml` немає, бо NP вже успадковується з base; додавайте лише якщо overlay змінює правила. **`check k8s`** звіряє прив'язку за іменами (і **дописує** відсутні NetworkPolicy-документи автоматично у файл поруч):
404
405
 
405
406
  - **`hpa.yaml`** (поза **`…/base/`**) — `autoscaling/v2`, `HorizontalPodAutoscaler`, `spec.scaleTargetRef.name` **= `metadata.name`** Deployment.
406
- - **`networkpolicy.yaml`** — той самий канон egress/ingress, що в `components/` (multi-doc, якщо у каталозі кілька workload-ів).
407
+ - **`networkpolicy.yaml`** (overlay-specific, опціональний) — той самий канон egress/ingress, що в `base/` (multi-doc, якщо у каталозі кілька workload-ів).
407
408
  - **`pdb.yaml`** — `policy/v1`, `PodDisruptionBudget`, `spec.selector.matchLabels.app` **= `spec.selector.matchLabels.app`** Deployment.
408
409
  - **`topologySpreadConstraints`** — запис з `maxSkew: 1`, `topologyKey: kubernetes.io/hostname`, `whenUnsatisfiable: ScheduleAnyway`, `labelSelector.matchLabels.app` рівне тій самій мітці `app`.
409
410
 
410
- **Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml`, `networkpolicy.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`check-k8s.mjs`**.
411
+ **Перевірка структури `components/`** (для кожного Deployment у `base/`): наявність каталогу, валідний `kustomization.yaml` як Component, `hpa.yaml` і `pdb.yaml` з відповідністю до Deployment-name / app-label. Алгоритм — функція `validateComponentsForBaseDeployment` у **`check-k8s.mjs`**.
411
412
 
412
413
  **Локальні шляхи в `kustomization.yaml`:** кожен запис без `://` (remote) з `resources` / `bases` / `components` / `crds`, `patchesStrategicMerge`, `patches[].path`, `patchesJson6902[].path`, `configurations[]`, `replacements[].path` має вказувати на **існуючий** у репозиторії файл (`.yaml` / `.yml`) або **каталог**; биті посилання — помилка **`check k8s`**.
413
414
 
@@ -462,18 +463,28 @@ patches:
462
463
  value: 1
463
464
  ```
464
465
 
465
- ### Приклади `components/`
466
+ ### Приклади `components/` і `base/networkpolicy.yaml`
466
467
 
467
468
  ```yaml title="k8s/components/kustomization.yaml"
468
469
  apiVersion: kustomize.config.k8s.io/v1alpha1
469
470
  kind: Component
470
471
  resources:
471
472
  - hpa.yaml
472
- - networkpolicy.yaml
473
473
  - pdb.yaml
474
474
  ```
475
475
 
476
- ```yaml title="k8s/components/networkpolicy.yaml"
476
+ ```yaml title="k8s/base/kustomization.yaml (фрагмент)"
477
+ apiVersion: kustomize.config.k8s.io/v1beta1
478
+ kind: Kustomization
479
+ namespace: dev
480
+ resources:
481
+ - deploy.yaml
482
+ - networkpolicy.yaml
483
+ - svc.yaml
484
+ - svc-hl.yaml
485
+ ```
486
+
487
+ ```yaml title="k8s/base/networkpolicy.yaml"
477
488
  # yaml-language-server: $schema=https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.33.9-standalone-strict/networkpolicy-networking-v1.json
478
489
  apiVersion: networking.k8s.io/v1
479
490
  kind: NetworkPolicy
@@ -227,10 +227,10 @@ function runKubescapeManifest(kubescapePath, manifest, exceptionsArgs) {
227
227
  * Запускає kubescape по зібраному kustomize-маніфесту для кожного `…/k8s`-кореня. Для кожного
228
228
  * dir-у з `kustomization.yaml` (крім `kind: Component`) робимо `kubectl kustomize <dir>` і
229
229
  * передаємо stdout у `kubescape scan <tmp-file>` через тимчасовий файл (kubescape v4.x не читає
230
- * stdin — див. `runKubescapeManifest`). Це усуває false-positive C-0260 (`Missing network policy`)
231
- * у випадках, коли NetworkPolicy живе у sibling `components/` без `metadata.namespace` (намспейс
232
- * інжектить overlay через `kustomization.namespace`); сирий dir-скан не виконує kustomize й бачить
233
- * порожній `namespace` у NetworkPolicy проти непорожнього у Deployment, через що `podSelector` не матчиться.
230
+ * stdin — див. `runKubescapeManifest`). Збірка через kustomize нормалізує namespace на workload-маніфестах
231
+ * і `base/networkpolicy.yaml` (через `base/kustomization.yaml` `namespace:`), що дає коректний
232
+ * матчинг `podSelector` у C-0260 (`Missing network policy`) і дозволяє kubescape бачити дерево
233
+ * overlays / components зі справжніми ресурсами.
234
234
  *
235
235
  * Якщо в `…/k8s`-корені немає жодного білдабельного kustomization.yaml (проєкт без Kustomize) —
236
236
  * fallback на старий dir-скан, щоб не блокувати чистий YAML-only набір маніфестів.
@@ -41,16 +41,20 @@ deny contains base_namespace_required_msg if {
41
41
  # (із зануренням у вкладені kustomization.yaml) — JS-оркестратор
42
42
  # `verifyK8sBaseKustomizeHasNoHpaPdb` у `check-k8s.mjs` (потребує fs-доступу). Цей
43
43
  # rego-deny — defense-in-depth: спрацює навіть якщо JS-крок упаде з винятку раніше.
44
- deny contains hpa_pdb_np_in_base_resources_msg(r) if {
44
+ #
45
+ # NetworkPolicy у base — навпаки, обов'язковий (k8s.mdc): обмеження мережі мають
46
+ # діяти і на dev-середовищі, тож `base/networkpolicy.yaml` підключений через
47
+ # `base/kustomization.yaml` `resources:` і не блокується цим правилом.
48
+ deny contains hpa_pdb_in_base_resources_msg(r) if {
45
49
  is_kustomization
46
50
  some r in object.get(input, "resources", [])
47
51
  is_string(r)
48
- is_hpa_pdb_or_np_filename(r)
52
+ is_hpa_or_pdb_filename(r)
49
53
  }
50
54
 
51
- hpa_pdb_np_in_base_resources_msg(file) := sprintf(
55
+ hpa_pdb_in_base_resources_msg(file) := sprintf(
52
56
  concat("", [
53
- "у base/kustomization.yaml `resources:` містить '%v' — HPA/PDB/NetworkPolicy заборонені у base, ",
57
+ "у base/kustomization.yaml `resources:` містить '%v' — HPA/PDB заборонені у base, ",
54
58
  "перенесіть у sibling каталог components/ і підключайте з overlay (k8s.mdc)",
55
59
  ]),
56
60
  [file],
@@ -61,14 +65,10 @@ is_kustomization if {
61
65
  startswith(object.get(input, "apiVersion", ""), "kustomize.config.k8s.io/")
62
66
  }
63
67
 
64
- is_hpa_pdb_or_np_filename(p) if {
68
+ is_hpa_or_pdb_filename(p) if {
65
69
  basename(p) in {
66
70
  "hpa.yaml",
67
71
  "pdb.yaml",
68
- "networkpolicy.yaml",
69
- "hpa.yml",
70
- "pdb.yml",
71
- "networkpolicy.yml",
72
72
  }
73
73
  }
74
74
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
3
  "files": {
4
- "walkGlob": ["**/k8s/**/base/**/*.yaml", "**/k8s/**/base/**/*.yml", "!**/k8s/**/base/**/kustomization.yaml"]
4
+ "walkGlob": ["**/k8s/**/base/**/*.yaml", "!**/k8s/**/base/**/kustomization.yaml"]
5
5
  }
6
6
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
- "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml"] }
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
- "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml"] }
4
4
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "$schema": "https://unpkg.com/@nitra/cursor/schemas/target.json",
3
- "files": { "walkGlob": ["**/k8s/**/*.yaml", "**/k8s/**/*.yml"] }
3
+ "files": { "walkGlob": ["**/k8s/**/*.yaml"] }
4
4
  }