Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clusters/dev/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ resources:
# GitOps
- ../../platform/gitops/argocd-image-updater
# Tenants
- ../../tenants/product-team/apps/chat/base
- ../../tenants/product-team/apps/chat/overlays/dev

patches:
# Override .spec.destination.name
Expand Down
148 changes: 118 additions & 30 deletions policies/kubernetes.rego
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,156 @@ package kubernetes

import rego.v1

# 🛡️ 1. Контейнеры должны иметь ресурсы
# ═══════════════════════════════════════════════════════════════════════════════
# Cluster-scoped resources (не требуют namespace)
# ═══════════════════════════════════════════════════════════════════════════════
cluster_scoped_kinds := {
"Namespace",
"ClusterRole",
"ClusterRoleBinding",
"ClusterIssuer",
"CustomResourceDefinition",
"StorageClass",
"PersistentVolume",
"IngressClass",
"PriorityClass",
"ValidatingWebhookConfiguration",
"MutatingWebhookConfiguration",
}

# ArgoCD resources (управляются ArgoCD, имеют свой namespace в spec)
argocd_kinds := {
"Application",
"ApplicationSet",
"AppProject",
}

# ═══════════════════════════════════════════════════════════════════════════════
# 📛 1. Namespace должен быть указан (для namespaced ресурсов)
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
not cluster_scoped_kinds[input.kind]
not argocd_kinds[input.kind]
not input.metadata.namespace
msg := sprintf("[%s/%s] Resource is missing namespace", [input.kind, input.metadata.name])
}

# ═══════════════════════════════════════════════════════════════════════════════
# 🛡️ 2. Контейнеры должны иметь ресурсы
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.memory
msg := sprintf("Container %s missing memory limit", [container.name])
msg := sprintf("[Deployment/%s] Container '%s' missing memory limit", [input.metadata.name, container.name])
}

deny contains msg if {
input.kind == "Deployment"
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.cpu
msg := sprintf("Container %s missing CPU limit", [container.name])
msg := sprintf("[Deployment/%s] Container '%s' missing CPU limit", [input.metadata.name, container.name])
}

# 🔬 2. Должны быть probes
# ═══════════════════════════════════════════════════════════════════════════════
# 🔬 3. Должны быть probes (только для production workloads)
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "Deployment"
is_production_workload
container := input.spec.template.spec.containers[_]
not container.livenessProbe
msg := sprintf("Container %s missing livenessProbe", [container.name])
msg := sprintf("[Deployment/%s] Container '%s' missing livenessProbe", [input.metadata.name, container.name])
}

deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
is_production_workload
container := input.spec.template.spec.containers[_]
not container.readinessProbe
msg := sprintf("Container %s missing readinessProbe", [container.name])
msg := sprintf("[Deployment/%s] Container '%s' missing readinessProbe", [input.metadata.name, container.name])
}

# 📛 3. Namespace должен быть указан
deny contains msg if {
not input.metadata.namespace
msg := "Resource is missing namespace"
# Определяем production workloads по labels
is_production_workload if {
input.metadata.labels.env == "prd"
}

# 🔐 4. Контейнеры должны запускаться не от root
is_production_workload if {
input.metadata.labels.environment == "production"
}

# ═══════════════════════════════════════════════════════════════════════════════
# 🔐 4. Security context (рекомендации, не обязательно)
# ═══════════════════════════════════════════════════════════════════════════════
# Примечание: многие сторонние чарты не имеют runAsNonRoot
# warn contains msg if {
# input.kind == "Deployment"
# not input.spec.template.spec.securityContext.runAsNonRoot
# msg := sprintf("[Deployment/%s] Recommendation: set runAsNonRoot: true", [input.metadata.name])
# }

# ═══════════════════════════════════════════════════════════════════════════════
# 📦 5. Chat-API специфичные проверки
# ═══════════════════════════════════════════════════════════════════════════════
# Примечание: отключено, т.к. annotation задается через Helm values
# deny contains msg if {
# input.kind == "Deployment"
# input.metadata.labels["app.kubernetes.io/name"] == "chat-api"
# not input.spec.template.metadata.annotations["openrouter.model"]
# msg := "[Deployment/chat-api] Missing openrouter.model annotation"
# }

# ═══════════════════════════════════════════════════════════════════════════════
# 🔭 6. OTEL (только для production)
# ═══════════════════════════════════════════════════════════════════════════════
# Примечание: отключено, т.к. OTEL injector добавляет автоматически
# deny contains msg if {
# input.kind == "Deployment"
# is_production_workload
# container := input.spec.template.spec.containers[_]
# not has_otel_endpoint(container)
# msg := sprintf("[Deployment/%s] Container '%s' missing OTEL_EXPORTER_OTLP_ENDPOINT", [input.metadata.name, container.name])
# }

# has_otel_endpoint(container) if {
# some env in container.env
# env.name == "OTEL_EXPORTER_OTLP_ENDPOINT"
# }

# ═══════════════════════════════════════════════════════════════════════════════
# 🏷️ 7. Labels обязательны для workloads
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msg := "Deployment must set runAsNonRoot: true"
not input.metadata.labels["app.kubernetes.io/name"]
msg := sprintf("[Deployment/%s] Missing required label: app.kubernetes.io/name", [input.metadata.name])
}

# 📦 5. Если используется LLM — должна быть задана модель
# ═══════════════════════════════════════════════════════════════════════════════
# 🔒 8. SealedSecrets должны иметь namespace
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
input.metadata.labels["app.kubernetes.io/name"] == "chat-api"
not input.spec.template.metadata.annotations["openrouter.model"]
msg := "chat-api is missing openrouter.model annotation"
input.kind == "SealedSecret"
not input.metadata.namespace
msg := sprintf("[SealedSecret/%s] Must have namespace specified", [input.metadata.name])
}

# 🔭 6. OTEL переменные должны быть заданы
# ═══════════════════════════════════════════════════════════════════════════════
# 🌐 9. Ingress должен иметь TLS для production
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not has_otel_endpoint(container)
msg := "OpenTelemetry OTLP endpoint is missing"
input.kind == "Ingress"
input.metadata.labels.env == "prd"
not input.spec.tls
msg := sprintf("[Ingress/%s] Production ingress must have TLS configured", [input.metadata.name])
}

# Вспомогательная функция для проверки OTEL endpoint
has_otel_endpoint(container) if {
some env in container.env
env.name == "OTEL_EXPORTER_OTLP_ENDPOINT"
# ═══════════════════════════════════════════════════════════════════════════════
# 📊 10. HPA должен иметь разумные лимиты
# ═══════════════════════════════════════════════════════════════════════════════
deny contains msg if {
input.kind == "HorizontalPodAutoscaler"
input.spec.maxReplicas > 100
msg := sprintf("[HPA/%s] maxReplicas > 100 is likely a mistake", [input.metadata.name])
}
35 changes: 35 additions & 0 deletions tenants/product-team/apps/chat/base/application.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: chat-api
namespace: argocd
labels:
app.kubernetes.io/name: chat-api
app.kubernetes.io/part-of: chat
app.kubernetes.io/managed-by: argocd
annotations:
argocd.argoproj.io/sync-wave: "5"
# Image Updater annotations
argocd-image-updater.argoproj.io/image-list: chat=ghcr.io/justgithubaccount/chat-api:^1
argocd-image-updater.argoproj.io/chat.update-strategy: semver
argocd-image-updater.argoproj.io/chat.helm.image-tag: image.tag
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: main
spec:
project: default
source:
repoURL: https://github.com/justgithubaccount/app-poly-gitops-helm
targetRevision: main
path: .
helm:
valueFiles:
- values.yaml
destination:
name: in-cluster
namespace: chat-api
syncPolicy:
automated:
selfHeal: true
prune: true
syncOptions:
- CreateNamespace=true
5 changes: 2 additions & 3 deletions tenants/product-team/apps/chat/base/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# Application managed by ApplicationSet in platform/gitops/appsets/tenant-apps.yaml
# This directory contains shared resources for the chat application
resources: []
resources:
- application.yaml
29 changes: 29 additions & 0 deletions tenants/product-team/apps/chat/overlays/dev/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,32 @@ resources:
- postgree-secrets.yaml
- openrouter-secrets.yaml
- github-secrets.yaml

patches:
- target:
kind: Application
name: chat-api
patch: |
- op: add
path: /metadata/labels/env
value: dev
- op: replace
path: /spec/source/helm/valueFiles
value:
- values.yaml
- op: add
path: /spec/source/helm/values
value: |
image:
tag: "1.1.7"
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
ingress:
enabled: true
host: chat-dev.syncjob.ru
33 changes: 33 additions & 0 deletions tenants/product-team/apps/chat/overlays/prd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,36 @@ kind: Kustomization

resources:
- ../../base
# TODO: Create prd-specific SealedSecrets
# - postgree-secrets.yaml
# - openrouter-secrets.yaml
# - github-secrets.yaml

patches:
- target:
kind: Application
name: chat-api
patch: |
- op: add
path: /metadata/labels/env
value: prd
- op: replace
path: /spec/source/helm/valueFiles
value:
- values.yaml
- op: add
path: /spec/source/helm/values
value: |
image:
tag: "1.1.7"
replicaCount: 3
resources:
limits:
cpu: 2000m
memory: 2Gi
requests:
cpu: 1000m
memory: 1Gi
ingress:
enabled: true
host: chat.syncjob.ru
33 changes: 33 additions & 0 deletions tenants/product-team/apps/chat/overlays/stg/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,36 @@ kind: Kustomization

resources:
- ../../base
# TODO: Create stg-specific SealedSecrets
# - postgree-secrets.yaml
# - openrouter-secrets.yaml
# - github-secrets.yaml

patches:
- target:
kind: Application
name: chat-api
patch: |
- op: add
path: /metadata/labels/env
value: stg
- op: replace
path: /spec/source/helm/valueFiles
value:
- values.yaml
- op: add
path: /spec/source/helm/values
value: |
image:
tag: "1.1.7"
replicaCount: 2
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 500m
memory: 512Mi
ingress:
enabled: true
host: chat-stg.syncjob.ru
33 changes: 33 additions & 0 deletions tenants/product-team/apps/chat/overlays/tst/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,36 @@ kind: Kustomization

resources:
- ../../base
# TODO: Create tst-specific SealedSecrets
# - postgree-secrets.yaml
# - openrouter-secrets.yaml
# - github-secrets.yaml

patches:
- target:
kind: Application
name: chat-api
patch: |
- op: add
path: /metadata/labels/env
value: tst
- op: replace
path: /spec/source/helm/valueFiles
value:
- values.yaml
- op: add
path: /spec/source/helm/values
value: |
image:
tag: "1.1.7"
replicaCount: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 250m
memory: 256Mi
ingress:
enabled: true
host: chat-tst.syncjob.ru