feat(ai): mcp-k8s через ToolHive — read-only доступ агента к кластеру #14

Open
hermes wants to merge 2 commits from feat/mcp-k8s into main
2 changed files with 56 additions and 83 deletions
Showing only changes of commit c2c9b273f2 - Show all commits
+20 -10
View File
@@ -1,21 +1,31 @@
--- ---
# Kubernetes MCP из реестра ToolHive (сервер k8s, проект StacklokLabs/mkp). # Kubernetes MCP Server: containers/kubernetes-mcp-server (Apache-2.0, Go, без обёрток над kubectl).
# Транспорт streamable-http — mkp отдаёт MCP по HTTP сам, прокси-обёртка над # Транспорт streamable-http — сервер сам отдаёт MCP по HTTP на --port 8080, путь /mcp,
# stdio не нужна. Порты оставлены дефолтными: proxyPort и mcpPort оба 8080. # ровно то, что ожидает прокси ToolHive (обёртка над stdio не нужна).
# #
# ВАЖНО: без oidcConfigRef прокси ToolHive работает без аутентификации — любой, # --read-only — штатный режим сервера: ни одной write-операции (create/update/delete).
# кто достучится до его порта, получит доступ к API под правами SA ниже. # Это второй барьер после RBAC SA (см. rbac.yaml); третий — denied_resources в TOML,
# Поэтому наружу сервер не выводим: пока агент вне кластера, адрес только # если понадобится спрятать ресурс от инструментов совсем.
# внутрикластерный, а внешний доступ появится вместе с OIDC после Authelia. #
# --toolsets core оставляет базовый набор: без helm (чарты ставит Flux, агенту там
# делать нечего), tekton, kubevirt, kiali и netobserv.
#
# ВАЖНО: без oidcConfigRef прокси ToolHive работает без аутентификации — любой, кто
# достучится до порта, получит доступ к API под правами SA ниже. Поэтому наружу сервер
# не выводим: внешний доступ появится вместе с OIDC после Authelia.
apiVersion: toolhive.stacklok.dev/v1beta1 apiVersion: toolhive.stacklok.dev/v1beta1
kind: MCPServer kind: MCPServer
metadata: metadata:
name: mcp-k8s name: mcp-k8s
spec: spec:
image: ghcr.io/stackloklabs/mkp/server:0.4.3 image: quay.io/containers/kubernetes_mcp_server:v0.0.66@sha256:6d650f4bd6ac303ad82713c997e73a2d001602f9bf17392c9b9a0e30e29c6423
transport: streamable-http transport: streamable-http
# SA существует заранее (см. serviceaccount.yaml) — вместе с ним приезжает args:
# ClusterRoleBinding mcp-k8s-readonly. - --port
- "8080"
- --read-only
- --toolsets
- core
serviceAccount: mcp-k8s serviceAccount: mcp-k8s
permissionProfile: permissionProfile:
type: builtin type: builtin
+36 -73
View File
@@ -1,83 +1,46 @@
--- ---
# Права MCP-сервера на API. Только чтение: mkp умеет и apply_resource, и # Базовое чтение даём встроенной ролью view — её же рекомендует официальный гайд
# delete_resource, и exec в поды — ограничение живёт исключительно здесь, # containers/kubernetes-mcp-server (docs/getting-started-kubernetes.md). view
# поэтому список verbs закрыт чтением. # описывает namespaced-ресурсы и secrets в неё не входят, поэтому именно она, а не
apiVersion: rbac.authorization.k8s.io/v1 # самодельный список с resources: ["*"].
kind: ClusterRole
metadata:
name: mcp-k8s-readonly
rules:
# Core-группа перечислена поимённо: resources: ["*"] открыл бы и secrets,
# а cluster-secrets, токен Proxmox CSI и прочие креды агенту читать незачем.
- apiGroups: [""]
resources:
- configmaps
- endpoints
- events
- limitranges
- namespaces
- nodes
- persistentvolumeclaims
- persistentvolumes
- pods
- pods/log
- pods/status
- replicationcontrollers
- resourcequotas
- serviceaccounts
- services
verbs: ["get", "list", "watch"]
# Остальные группы перечислены явно, потому что apiGroups: ["*"] включает и
# core-группу — то есть снова открыл бы secrets. Список закрытый: не хватает
# группы — сервер просто не увидит её ресурсы, ничего не сломается.
- apiGroups:
- admissionregistration.k8s.io
- apiextensions.k8s.io
- apiregistration.k8s.io
- apps
- authentication.k8s.io
- authorization.k8s.io
- autoscaling
- batch
- certificates.k8s.io
- coordination.k8s.io
- discovery.k8s.io
- events.k8s.io
- flowcontrol.apiserver.k8s.io
- metrics.k8s.io
- networking.k8s.io
- node.k8s.io
- policy
- rbac.authorization.k8s.io
- scheduling.k8s.io
- storage.k8s.io
- acme.cert-manager.io
- cert-manager.io
- cilium.io
- dragonflydb.io
- external-secrets.io
- gateway.envoyproxy.io
- gateway.networking.k8s.io
- generators.external-secrets.io
- helm.toolkit.fluxcd.io
- image.toolkit.fluxcd.io
- kustomize.toolkit.fluxcd.io
- monitoring.coreos.com
- notification.toolkit.fluxcd.io
- snapshot.storage.k8s.io
- source.toolkit.fluxcd.io
- toolhive.stacklok.dev
resources: ["*"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: mcp-k8s-readonly name: mcp-k8s-view
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
kind: ClusterRole kind: ClusterRole
name: mcp-k8s-readonly name: view
subjects:
- kind: ServiceAccount
name: mcp-k8s
namespace: ai
---
# view включает только namespaced-ресурсы, поэтому кластерные объекты и метрики
# добавляем отдельно. Ни secrets, ни записи здесь нет.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: mcp-k8s-cluster-read
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses", "volumeattachments", "csidrivers", "csistoragecapacities"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["*"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: mcp-k8s-cluster-read
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: mcp-k8s-cluster-read
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: mcp-k8s name: mcp-k8s