├── broker ├── .env ├── VERSION ├── db │ ├── migrations │ │ ├── .keep │ │ ├── 2023-06-06-070736_pod_traffic │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 2025-11-11-085418_add_pod_identity │ │ │ ├── down.sql │ │ │ └── up.sql │ │ ├── 2025-11-29-091312-0000_add_workload_selector_labels │ │ │ ├── up.sql │ │ │ └── down.sql │ │ └── 00000000000000_diesel_initial_setup │ │ │ ├── down.sql │ │ │ └── up.sql │ ├── diesel.toml │ └── install.md ├── README.md ├── test │ └── post.json ├── diesel.toml ├── src │ ├── conn.rs │ ├── telemetry.rs │ ├── lib.rs │ ├── error.rs │ ├── schema.rs │ └── main.rs ├── Dockerfile └── Cargo.toml ├── frontend ├── .nvmrc ├── VERSION ├── src │ ├── components │ │ ├── PolicyEditor │ │ │ └── index.ts │ │ ├── ThemeToggle.tsx │ │ └── NamespaceSelector.tsx │ ├── assets │ │ └── logo.png │ ├── hooks │ │ ├── policyEditor │ │ │ ├── index.ts │ │ │ └── usePolicyExport.ts │ │ └── useNamespaces.ts │ ├── main.tsx │ ├── App.css │ ├── constants │ │ └── ui.ts │ ├── types │ │ ├── networkPolicy.ts │ │ ├── index.ts │ │ └── seccompProfile.ts │ ├── contexts │ │ └── ThemeContext.tsx │ ├── services │ │ └── aiApi.ts │ ├── index.css │ └── utils │ │ └── seccompProfileGenerator.ts ├── postcss.config.js ├── tsconfig.json ├── .gitignore ├── index.html ├── tailwind.config.js ├── .dockerignore ├── eslint.config.js ├── tsconfig.node.json ├── tsconfig.app.json ├── package.json ├── Dockerfile ├── public │ └── vite.svg └── vite.config.ts ├── advisor ├── VERSION ├── main.go ├── pkg │ ├── k8s │ │ ├── errors.go │ │ ├── labels_test.go │ │ ├── portforward_test.go │ │ ├── generic.go │ │ └── labels.go │ ├── api │ │ └── pod_syscall.go │ └── network │ │ └── types_test.go ├── Dockerfile └── cmd │ └── version.go ├── controller ├── VERSION ├── .gitignore ├── .cargo │ └── Config.toml ├── src │ ├── lib.rs │ ├── log.rs │ ├── client.rs │ ├── error.rs │ └── bpf │ │ ├── syscall.bpf.c │ │ └── helper.h ├── Cross.toml ├── Dockerfile ├── Cargo.toml └── build.rs ├── charts └── kguardian │ ├── templates │ ├── NOTES.txt │ ├── clusterrolebinding.yaml │ ├── broker │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── deployment.yaml │ ├── database │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── deployment.yaml │ ├── frontend │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── ingress.yaml │ ├── llm-bridge │ │ ├── service.yaml │ │ └── serviceaccount.yaml │ ├── controller │ │ └── serviceaccount.yaml │ ├── tests │ │ └── test-connection.yaml │ ├── mcp-server │ │ ├── service.yaml │ │ ├── serviceaccount.yaml │ │ └── mcpserver-crd.yaml │ ├── clusterrole.yaml │ └── _helpers.tpl │ ├── .helmignore │ ├── Chart.yaml │ └── README.md.gotmpl ├── docs ├── logo │ ├── dark.png │ ├── light.png │ ├── light.svg │ └── dark.svg ├── api-reference │ ├── endpoint │ │ ├── get.mdx │ │ ├── create.mdx │ │ ├── delete.mdx │ │ └── webhook.mdx │ ├── endpoints │ │ ├── syscalls.mdx │ │ ├── services.mdx │ │ ├── traffic.mdx │ │ └── pods.mdx │ └── introduction.mdx ├── images │ ├── hero-dark.png │ ├── hero-light.png │ └── checks-passed.png ├── snippets │ └── snippet-intro.mdx ├── essentials │ ├── code.mdx │ ├── images.mdx │ └── navigation.mdx ├── LICENSE ├── favicon.svg ├── README.md ├── cli │ ├── overview.mdx │ ├── gen-networkpolicy.mdx │ └── gen-seccomp.mdx ├── concepts │ ├── seccomp-profiles.mdx │ ├── ebpf-monitoring.mdx │ ├── network-policies.mdx │ └── overview.mdx └── ai-tools │ └── claude-code.mdx ├── mcp-server ├── .dockerignore ├── .env.example ├── .gitignore ├── tools │ ├── tools.go │ ├── cluster_pods.go │ ├── pod_details.go │ ├── service_details.go │ ├── syscalls.go │ ├── cluster_traffic.go │ └── network_traffic.go ├── go.mod ├── tsconfig.json ├── package.json ├── Dockerfile ├── logger │ └── logger.go ├── go.sum └── main.go ├── .github ├── CODEOWNERS ├── ct.yaml ├── renovate │ └── autoMerge.json5 ├── renovate.json5 └── workflows │ ├── release-please.yaml │ ├── charts-docs.yaml │ ├── charts-release.yaml │ ├── charts-lint.yaml │ ├── advisor-test.yaml │ ├── frontend-release.yaml │ ├── mcp-server-release.yaml │ ├── llm-bridge-release.yaml │ ├── broker-release.yaml │ └── renovate.yaml ├── llm-bridge ├── .gitignore ├── .env.example ├── tsconfig.json ├── .dockerignore ├── Dockerfile ├── package.json ├── src │ └── types │ │ └── index.ts └── CHANGELOG.md ├── .release-please-manifest.json ├── .taskfiles ├── Advisor │ └── Taskfile.yaml ├── Broker │ └── Taskfile.yaml ├── UI │ └── Taskfile.yaml └── Controller │ └── Taskfile.yaml ├── .gitignore ├── .slsa-goreleaser └── advisor │ ├── linux-amd64.yml │ ├── linux-arm64.yml │ ├── darwin-amd64.yml │ └── darwin-arm64.yml ├── .versionrc.json ├── Taskfile.yaml └── scripts └── quick-install.sh /broker/.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 24 2 | -------------------------------------------------------------------------------- /advisor/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /broker/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /broker/db/migrations/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /controller/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /frontend/VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | -------------------------------------------------------------------------------- /charts/kguardian/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /controller/.gitignore: -------------------------------------------------------------------------------- 1 | **/target/** 2 | localbin 3 | .vscode -------------------------------------------------------------------------------- /broker/db/diesel.toml: -------------------------------------------------------------------------------- 1 | 2 | [print_schema] 3 | file = "../src/schema.rs" 4 | -------------------------------------------------------------------------------- /docs/logo/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/docs/logo/dark.png -------------------------------------------------------------------------------- /docs/logo/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/docs/logo/light.png -------------------------------------------------------------------------------- /frontend/src/components/PolicyEditor/index.ts: -------------------------------------------------------------------------------- 1 | export { PolicyHeader } from './PolicyHeader'; 2 | -------------------------------------------------------------------------------- /controller/.cargo/Config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-linux-gnu-gcc" -------------------------------------------------------------------------------- /docs/api-reference/endpoint/get.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Get Plants' 3 | openapi: 'GET /plants' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/api-reference/endpoint/create.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Create Plant' 3 | openapi: 'POST /plants' 4 | --- 5 | -------------------------------------------------------------------------------- /mcp-server/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | .env 5 | .git 6 | .gitignore 7 | README.md 8 | -------------------------------------------------------------------------------- /broker/README.md: -------------------------------------------------------------------------------- 1 | DOCKER_BUILDKIT=1 docker build . -t ghcr.io/kguardian-dev/kguardian/guardian-broker:latest 2 | -------------------------------------------------------------------------------- /docs/images/hero-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/docs/images/hero-dark.png -------------------------------------------------------------------------------- /docs/images/hero-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/docs/images/hero-light.png -------------------------------------------------------------------------------- /docs/api-reference/endpoint/delete.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Delete Plant' 3 | openapi: 'DELETE /plants/{id}' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/api-reference/endpoint/webhook.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'New Plant' 3 | openapi: 'WEBHOOK /plant/webhook' 4 | --- 5 | -------------------------------------------------------------------------------- /docs/images/checks-passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/docs/images/checks-passed.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kguardian-dev/kguardian/HEAD/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /broker/db/migrations/2023-06-06-070736_pod_traffic/down.sql: -------------------------------------------------------------------------------- 1 | -- This file should undo anything in `up.sql` 2 | DROP TABLE pod_traffic; 3 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 2 | * @xUnholy @maheshrayas 3 | -------------------------------------------------------------------------------- /advisor/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/kguardian-dev/kguardian/advisor/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /broker/db/migrations/2025-11-11-085418_add_pod_identity/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove pod_identity column from pod_details table 2 | ALTER TABLE pod_details DROP COLUMN pod_identity; 3 | -------------------------------------------------------------------------------- /broker/db/migrations/2025-11-11-085418_add_pod_identity/up.sql: -------------------------------------------------------------------------------- 1 | -- Add pod_identity column to pod_details table 2 | ALTER TABLE pod_details ADD COLUMN pod_identity VARCHAR; 3 | -------------------------------------------------------------------------------- /broker/db/migrations/2025-11-29-091312-0000_add_workload_selector_labels/up.sql: -------------------------------------------------------------------------------- 1 | -- Add workload_selector_labels column to pod_details table 2 | ALTER TABLE pod_details ADD COLUMN workload_selector_labels JSON; 3 | -------------------------------------------------------------------------------- /broker/db/migrations/2025-11-29-091312-0000_add_workload_selector_labels/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove workload_selector_labels column from pod_details table 2 | ALTER TABLE pod_details DROP COLUMN workload_selector_labels; 3 | -------------------------------------------------------------------------------- /mcp-server/.env.example: -------------------------------------------------------------------------------- 1 | # MCP Server Configuration 2 | 3 | # Broker API URL (required) 4 | BROKER_URL=http://broker.kguardian.svc.cluster.local:9090 5 | 6 | # For local development 7 | # BROKER_URL=http://localhost:9090 8 | -------------------------------------------------------------------------------- /broker/test/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "3313-113-1313-13-131", 3 | "src_ip": "10.09.10.11:443", 4 | "src_pod_name": "nginx", 5 | "src_pod_namespace": "kube-system", 6 | "dst_ip": "10.09.10.10:443", 7 | "time_stamp": "2007-04-05T14:30:30" 8 | } 9 | -------------------------------------------------------------------------------- /broker/diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see https://diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/schema.rs" 6 | custom_type_derives = ["diesel::query_builder::QueryId"] 7 | 8 | [migrations_directory] 9 | dir = "migrations" 10 | -------------------------------------------------------------------------------- /docs/snippets/snippet-intro.mdx: -------------------------------------------------------------------------------- 1 | One of the core principles of software development is DRY (Don't Repeat 2 | Yourself). This is a principle that applies to documentation as 3 | well. If you find yourself repeating the same content in multiple places, you 4 | should consider creating a custom snippet to keep your content in sync. 5 | -------------------------------------------------------------------------------- /frontend/src/hooks/policyEditor/index.ts: -------------------------------------------------------------------------------- 1 | export { useNetworkPolicyEditor } from './useNetworkPolicyEditor'; 2 | export { useSeccompProfileEditor } from './useSeccompProfileEditor'; 3 | export { useSyscallAutocomplete } from './useSyscallAutocomplete'; 4 | export { usePolicyExport, type PolicyType } from './usePolicyExport'; 5 | -------------------------------------------------------------------------------- /controller/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod network; 2 | pub mod syscall; 3 | 4 | pub mod error; 5 | pub mod pod_watcher; 6 | pub mod pod_reconciler; 7 | pub mod service_watcher; 8 | use error::*; 9 | 10 | pub mod models; 11 | use models::*; 12 | pub mod client; 13 | pub mod container; 14 | use client::*; 15 | 16 | pub mod bpf; 17 | pub mod log; 18 | -------------------------------------------------------------------------------- /advisor/pkg/k8s/errors.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Common error definitions for the k8s package 8 | var ( 9 | ErrNoClientset = errors.New("no Kubernetes clientset available") 10 | ErrInvalidInput = errors.New("invalid input parameters") 11 | ErrNoConfig = errors.New("no Kubernetes configuration available") 12 | ) 13 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.github/ct.yaml: -------------------------------------------------------------------------------- 1 | # Chart Testing Configuration 2 | # https://github.com/helm/chart-testing/blob/main/doc/ct_lint.md 3 | 4 | # Disable chart version increment check since we use release-please for automated versioning 5 | check-version-increment: false 6 | 7 | # Path to charts directory 8 | chart-dirs: 9 | - charts 10 | 11 | # Target branch for comparing changes 12 | target-branch: main 13 | -------------------------------------------------------------------------------- /broker/db/migrations/00000000000000_diesel_initial_setup/down.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); 6 | DROP FUNCTION IF EXISTS diesel_set_updated_at(); 7 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.tsx' 5 | import { ThemeProvider } from './contexts/ThemeContext.tsx' 6 | 7 | createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | 12 | , 13 | ) 14 | -------------------------------------------------------------------------------- /llm-bridge/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.*.local 11 | 12 | # Logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # IDE 19 | .vscode/ 20 | .idea/ 21 | *.swp 22 | *.swo 23 | *~ 24 | 25 | # OS 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # TypeScript 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /mcp-server/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | dist/ 6 | 7 | # Environment variables 8 | .env 9 | .env.local 10 | .env.*.local 11 | 12 | # Logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # IDE 19 | .vscode/ 20 | .idea/ 21 | *.swp 22 | *.swo 23 | *~ 24 | 25 | # OS 26 | .DS_Store 27 | Thumbs.db 28 | 29 | # TypeScript 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /broker/src/conn.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | 3 | use diesel::pg::PgConnection; 4 | use diesel::r2d2::ConnectionManager; 5 | use dotenv::dotenv; 6 | use std::env; 7 | 8 | pub fn establish_connection() -> ConnectionManager { 9 | dotenv().ok(); 10 | 11 | let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); 12 | ConnectionManager::::new(database_url) 13 | } 14 | -------------------------------------------------------------------------------- /broker/src/telemetry.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | pub fn init_logging() { 4 | // check the rust log 5 | if env::var("RUST_LOG").is_err() { 6 | env::set_var("RUST_LOG", "info") 7 | } 8 | 9 | // Initialize the logger 10 | tracing_subscriber::fmt() 11 | .with_writer(std::io::stderr) 12 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 13 | .init(); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | kguardian 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /mcp-server/tools/tools.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/modelcontextprotocol/go-sdk/mcp" 5 | ) 6 | 7 | // RegisterTools registers all kguardian MCP tools with the server. 8 | // This is a compatibility wrapper that delegates to RegisterAllTools. 9 | // Deprecated: Use RegisterAllTools directly for better kmcp integration. 10 | func RegisterTools(server *mcp.Server, brokerURL string) { 11 | RegisterAllTools(server, brokerURL) 12 | } 13 | -------------------------------------------------------------------------------- /charts/kguardian/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "controller": "1.6.0", 3 | "controller+FILLER": "0.0.0", 4 | "broker": "1.5.0", 5 | "broker+FILLER": "0.0.0", 6 | "frontend": "1.6.1", 7 | "frontend+FILLER": "0.0.0", 8 | "advisor": "1.1.2", 9 | "advisor+FILLER": "0.0.0", 10 | "charts/kguardian": "1.7.0", 11 | "charts/kguardian+FILLER": "0.0.0", 12 | "llm-bridge": "1.1.0", 13 | "llm-bridge+FILLER": "0.0.0", 14 | "mcp-server": "1.2.1", 15 | "mcp-server+FILLER": "0.0.0" 16 | } 17 | -------------------------------------------------------------------------------- /charts/kguardian/templates/clusterrolebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: {{ include "kguardian.fullname" . }} 5 | subjects: 6 | - kind: ServiceAccount 7 | name: {{ default "controller" .Values.controller.serviceAccount.name }} 8 | namespace: {{ include "kguardian.namespace" . }} 9 | roleRef: 10 | kind: ClusterRole 11 | name: {{ include "kguardian.name" . }}-viewer 12 | apiGroup: rbac.authorization.k8s.io 13 | -------------------------------------------------------------------------------- /mcp-server/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kguardian-dev/kguardian/mcp-server 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.25.5 6 | 7 | require ( 8 | github.com/modelcontextprotocol/go-sdk v1.1.0 9 | github.com/sirupsen/logrus v1.9.3 10 | ) 11 | 12 | require ( 13 | github.com/google/jsonschema-go v0.3.0 // indirect 14 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 15 | golang.org/x/oauth2 v0.30.0 // indirect 16 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /charts/kguardian/templates/broker/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Values.broker.service.name }} 6 | labels: 7 | {{- include "kguardian.labels" . | nindent 4 }} 8 | spec: 9 | type: {{ .Values.broker.service.type }} 10 | ports: 11 | - port: {{ .Values.broker.service.port }} 12 | targetPort: http 13 | protocol: TCP 14 | name: http 15 | selector: 16 | app.kubernetes.io/name: {{ .Values.broker.service.name }} 17 | -------------------------------------------------------------------------------- /charts/kguardian/templates/database/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Values.database.service.name }} 6 | labels: 7 | {{- include "kguardian.labels" . | nindent 4 }} 8 | spec: 9 | type: {{ .Values.database.service.type }} 10 | ports: 11 | - port: {{ .Values.database.service.port }} 12 | # targetPort: postgresdb 13 | protocol: TCP 14 | name: http 15 | selector: 16 | app.kubernetes.io/name: {{ .Values.database.service.name }} 17 | -------------------------------------------------------------------------------- /charts/kguardian/templates/broker/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.broker.serviceAccount.create -}} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ default "broker" .Values.broker.serviceAccount.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | {{- with .Values.broker.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | automountServiceAccountToken: {{ .Values.broker.serviceAccount.automountServiceAccountToken }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /charts/kguardian/templates/frontend/service.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ .Values.frontend.service.name }} 6 | namespace: {{ include "kguardian.namespace" . }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | spec: 10 | type: {{ .Values.frontend.service.type }} 11 | ports: 12 | - port: {{ .Values.frontend.service.port }} 13 | targetPort: http 14 | protocol: TCP 15 | name: http 16 | selector: 17 | app.kubernetes.io/name: {{ .Values.frontend.service.name }} 18 | -------------------------------------------------------------------------------- /mcp-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | -------------------------------------------------------------------------------- /.taskfiles/Advisor/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | tasks: 6 | all: 7 | desc: "Run all advisor tasks" 8 | cmds: 9 | - task: advisor:install 10 | 11 | install: 12 | desc: "Install Advisor" 13 | cmds: 14 | - echo "Installing Advisor..." 15 | 16 | preflight: 17 | desc: "Run preflight checks for Advisor" 18 | cmds: 19 | - command -v go || echo "Go is not installed" 20 | - command -v golangci-lint || echo "golangci-lint is not installed" 21 | -------------------------------------------------------------------------------- /charts/kguardian/templates/database/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.database.serviceAccount.create -}} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ default "database" .Values.database.serviceAccount.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | {{- with .Values.database.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | automountServiceAccountToken: {{ .Values.database.serviceAccount.automountServiceAccountToken }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /charts/kguardian/templates/llm-bridge/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.llmBridge.enabled }} 2 | --- 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: {{ .Values.llmBridge.service.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | spec: 10 | type: {{ .Values.llmBridge.service.type }} 11 | ports: 12 | - port: {{ .Values.llmBridge.service.port }} 13 | targetPort: http 14 | protocol: TCP 15 | name: http 16 | selector: 17 | app.kubernetes.io/name: {{ .Values.llmBridge.service.name }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /llm-bridge/.env.example: -------------------------------------------------------------------------------- 1 | # LLM Bridge Configuration 2 | 3 | # Server Configuration 4 | PORT=8080 5 | NODE_ENV=development 6 | 7 | # Broker API URL 8 | BROKER_URL=http://broker.kguardian.svc.cluster.local:9090 9 | 10 | # For local development 11 | # BROKER_URL=http://localhost:9090 12 | 13 | # LLM Provider API Keys (configure at least one) 14 | 15 | # OpenAI 16 | # OPENAI_API_KEY=sk-... 17 | 18 | # Anthropic Claude 19 | # ANTHROPIC_API_KEY=sk-ant-... 20 | 21 | # Google Gemini 22 | # GOOGLE_API_KEY=AIza... 23 | 24 | # GitHub Copilot 25 | # GITHUB_TOKEN=ghp_... 26 | -------------------------------------------------------------------------------- /charts/kguardian/templates/llm-bridge/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.llmBridge.serviceAccount.create -}} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ default "llm-bridge" .Values.llmBridge.serviceAccount.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | {{- with .Values.llmBridge.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | automountServiceAccountToken: {{ .Values.llmBridge.serviceAccount.automountServiceAccountToken }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /broker/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod add; 2 | mod error; 3 | mod get; 4 | mod telemetry; 5 | mod types; 6 | pub use add::{add_pod_details, add_pods, add_pods_batch, add_pods_syscalls, add_svc_details, mark_pod_dead}; 7 | pub use error::*; 8 | pub use telemetry::*; 9 | pub use types::*; 10 | mod conn; 11 | pub use conn::*; 12 | mod schema; 13 | pub use get::{ 14 | get_pod_by_ip, get_pod_by_name, get_pod_details, get_pod_syscall_name, get_pod_traffic, get_pod_traffic_name, 15 | get_pods_by_node, get_svc_by_ip, 16 | }; 17 | pub use schema::{pod_details, pod_packet_drop, pod_traffic}; 18 | -------------------------------------------------------------------------------- /charts/kguardian/templates/controller/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.controller.serviceAccount.create -}} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ default "controller" .Values.controller.serviceAccount.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | {{- with .Values.controller.serviceAccount.annotations }} 10 | annotations: 11 | {{- toYaml . | nindent 4 }} 12 | {{- end }} 13 | automountServiceAccountToken: {{ .Values.controller.serviceAccount.automountServiceAccountToken }} 14 | {{- end }} 15 | -------------------------------------------------------------------------------- /charts/kguardian/templates/tests/test-connection.yaml: -------------------------------------------------------------------------------- 1 | {{/* 2 | TODO: Ignore this file for now. It's not used yet. 3 | 4 | apiVersion: v1 5 | kind: Pod 6 | metadata: 7 | name: "{{ include "kguardian.fullname" . }}-test-connection" 8 | labels: 9 | {{- include "kguardian.labels" . | nindent 4 }} 10 | annotations: 11 | "helm.sh/hook": test 12 | spec: 13 | containers: 14 | - name: wget 15 | image: busybox 16 | command: ['wget'] 17 | args: ['{{ include "kguardian.fullname" . }}:{{ .Values.service.port }}'] 18 | restartPolicy: Never 19 | */}} 20 | -------------------------------------------------------------------------------- /llm-bridge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "declaration": true, 14 | "declarationMap": true, 15 | "sourceMap": true, 16 | "types": ["node"] 17 | }, 18 | "include": ["src/**/*"], 19 | "exclude": ["node_modules", "dist"] 20 | } 21 | -------------------------------------------------------------------------------- /charts/kguardian/templates/mcp-server/service.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.mcpServer.enabled (not .Values.mcpServer.useKmcp) }} 2 | --- 3 | apiVersion: v1 4 | kind: Service 5 | metadata: 6 | name: {{ .Values.mcpServer.service.name }} 7 | labels: 8 | {{- include "kguardian.labels" . | nindent 4 }} 9 | spec: 10 | type: {{ .Values.mcpServer.service.type }} 11 | ports: 12 | - port: {{ .Values.mcpServer.service.port }} 13 | targetPort: http 14 | protocol: TCP 15 | name: http 16 | selector: 17 | app.kubernetes.io/name: {{ .Values.mcpServer.service.name }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /broker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:latest AS build 2 | 3 | COPY Cargo.lock /app/ 4 | 5 | RUN cargo new /app/broker 6 | 7 | COPY Cargo.toml /app/broker/ 8 | 9 | RUN apt update && apt-get install protobuf-compiler -y 10 | 11 | WORKDIR /app/broker 12 | 13 | COPY src /app/broker/src 14 | COPY db /app/broker/db 15 | 16 | RUN --mount=type=cache,target=/usr/local/cargo/registry cargo build --release 17 | 18 | FROM debian:bookworm-slim AS app 19 | 20 | RUN apt update && apt install libpq5 -y 21 | 22 | COPY --from=build /app/broker/target/release/broker /broker 23 | 24 | EXPOSE 9090 25 | 26 | CMD ["/broker"] 27 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | 'hubble-dark': '#0E1726', 11 | 'hubble-darker': '#0A0F1C', 12 | 'hubble-card': '#1A2332', 13 | 'hubble-border': '#2A3647', 14 | 'hubble-accent': '#3B82F6', 15 | 'hubble-success': '#10B981', 16 | 'hubble-warning': '#F59E0B', 17 | 'hubble-error': '#EF4444', 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | } 23 | -------------------------------------------------------------------------------- /charts/kguardian/templates/frontend/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.frontend.serviceAccount.create -}} 2 | --- 3 | apiVersion: v1 4 | kind: ServiceAccount 5 | metadata: 6 | name: {{ default "frontend" .Values.frontend.serviceAccount.name }} 7 | namespace: {{ include "kguardian.namespace" . }} 8 | labels: 9 | {{- include "kguardian.labels" . | nindent 4 }} 10 | {{- with .Values.frontend.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: {{ .Values.frontend.serviceAccount.automountServiceAccountToken }} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # rust target 24 | **/target/** 25 | *.tgz 26 | -------------------------------------------------------------------------------- /charts/kguardian/templates/clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: {{ include "kguardian.name" . }}-viewer 5 | labels: 6 | {{- include "kguardian.labels" . | nindent 4 }} 7 | {{- include "kguardian.annotations" . | nindent 2 }} 8 | rules: 9 | - apiGroups: [""] 10 | resources: ["namespaces", "pods", "services"] 11 | verbs: 12 | - get 13 | - watch 14 | - list 15 | - apiGroups: ["apps"] 16 | resources: 17 | - deployments 18 | - replicasets 19 | - statefulsets 20 | - daemonsets 21 | verbs: 22 | - get 23 | - list 24 | - watch 25 | -------------------------------------------------------------------------------- /charts/kguardian/templates/mcp-server/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.mcpServer.enabled }} 2 | {{- if .Values.mcpServer.serviceAccount.create -}} 3 | --- 4 | apiVersion: v1 5 | kind: ServiceAccount 6 | metadata: 7 | name: {{ default "mcp-server" .Values.mcpServer.serviceAccount.name }} 8 | labels: 9 | {{- include "kguardian.labels" . | nindent 4 }} 10 | {{- with .Values.mcpServer.serviceAccount.annotations }} 11 | annotations: 12 | {{- toYaml . | nindent 4 }} 13 | {{- end }} 14 | automountServiceAccountToken: {{ .Values.mcpServer.serviceAccount.automountServiceAccountToken }} 15 | {{- end }} 16 | {{- end }} 17 | -------------------------------------------------------------------------------- /.github/renovate/autoMerge.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | packageRules: [ 4 | { 5 | description: "Auto-merge GitHub Actions", 6 | matchManagers: ["github-actions"], 7 | automerge: true, 8 | automergeType: "branch", 9 | matchUpdateTypes: ["minor", "patch", "digest"], 10 | ignoreTests: true 11 | }, 12 | { 13 | description: "Auto-merge Helm Release", 14 | matchDatasources: ["helm", "docker"], 15 | automerge: true, 16 | automergeType: "pr", 17 | matchUpdateTypes: ["patch"], 18 | ignoreTests: true 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build output 8 | dist 9 | build 10 | .vite 11 | 12 | # Development files 13 | .env 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | # IDE 20 | .vscode 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | 26 | # Testing 27 | coverage 28 | .nyc_output 29 | 30 | # Git 31 | .git 32 | .gitignore 33 | 34 | # Documentation 35 | *.md 36 | !README.md 37 | 38 | # CI/CD 39 | .github 40 | .gitlab-ci.yml 41 | 42 | # Docker 43 | Dockerfile 44 | .dockerignore 45 | 46 | # Misc 47 | .DS_Store 48 | *.log 49 | -------------------------------------------------------------------------------- /llm-bridge/.dockerignore: -------------------------------------------------------------------------------- 1 | # Dependencies (not needed in build context) 2 | node_modules/ 3 | 4 | # Build artifacts (will be generated during build) 5 | dist/ 6 | 7 | # Environment files (contain secrets) 8 | .env 9 | .env.* 10 | 11 | # Logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Git directory 18 | .git/ 19 | 20 | # Documentation (not needed for runtime) 21 | *.md 22 | README.md 23 | 24 | # IDE and development files 25 | .vscode/ 26 | .idea/ 27 | *.swp 28 | *.swo 29 | *~ 30 | 31 | # OS files 32 | .DS_Store 33 | Thumbs.db 34 | 35 | # CI/CD files 36 | .github/ 37 | 38 | # Other config files not needed in image 39 | .prettierrc 40 | .eslintrc* 41 | .editorconfig 42 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | import { defineConfig, globalIgnores } from 'eslint/config' 7 | 8 | export default defineConfig([ 9 | globalIgnores(['dist']), 10 | { 11 | files: ['**/*.{ts,tsx}'], 12 | extends: [ 13 | js.configs.recommended, 14 | tseslint.configs.recommended, 15 | reactHooks.configs['recommended-latest'], 16 | reactRefresh.configs.vite, 17 | ], 18 | languageOptions: { 19 | ecmaVersion: 2020, 20 | globals: globals.browser, 21 | }, 22 | }, 23 | ]) 24 | -------------------------------------------------------------------------------- /controller/Cross.toml: -------------------------------------------------------------------------------- 1 | 2 | 3 | [target.aarch64-unknown-linux-gnu] 4 | image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:edge" 5 | 6 | pre-build = [ 7 | "dpkg --add-architecture $CROSS_DEB_ARCH", 8 | "apt-get update -y", 9 | "apt-get install -y libelf-dev:arm64 zlib1g-dev:arm64 gcc-aarch64-linux-gnu protobuf-compiler libseccomp-dev:arm64 libbpf-dev libc6-dev-arm64-cross clang" 10 | ] 11 | 12 | [target.x86_64-unknown-linux-gnu] 13 | image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:edge" 14 | pre-build = [ 15 | "dpkg --add-architecture $CROSS_DEB_ARCH", 16 | "apt-get update", 17 | "apt-get install -y libelf-dev zlib1g-dev gcc-multilib protobuf-compiler libseccomp-dev libbpf-dev libc6 clang", 18 | "apt-get clean" 19 | ] -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2023", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "types": ["node"], 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "erasableSyntaxOnly": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["vite.config.ts"] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "docker:enableMajor", 6 | "helpers:pinGitHubActionDigests", 7 | "security:openssf-scorecard", 8 | ":automergeBranch", 9 | ":automergeDigest", 10 | ":disableRateLimiting", 11 | ":dependencyDashboard", 12 | ":semanticCommits", 13 | ":timezone(Australia/Melbourne)", 14 | "github>kguardian-dev/kguardian//.github/renovate/autoMerge.json5", 15 | ], 16 | "dependencyDashboardTitle": "Renovate Dashboard 🤖", 17 | "dependencyDashboardAutoclose": true, 18 | "configWarningReuseIssue": true, 19 | "suppressNotifications": ["prEditedNotification", "prIgnoreNotification"], 20 | "platformAutomerge": true, 21 | "pre-commit": { 22 | "enabled": true 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Release Please 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | branches: 9 | - main 10 | 11 | permissions: 12 | contents: write 13 | issues: write 14 | pull-requests: write 15 | 16 | jobs: 17 | release-please: 18 | runs-on: ubuntu-latest 19 | # Skip if this is a release-please's own commit to avoid duplicate PRs 20 | if: "!startsWith(github.event.head_commit.message, 'chore(main): release')" 21 | steps: 22 | - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 23 | with: 24 | config-file: release-please-config.json 25 | manifest-file: .release-please-manifest.json 26 | token: ${{ secrets.KGUARDIAN_PAT }} 27 | -------------------------------------------------------------------------------- /.taskfiles/Broker/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | vars: 5 | BROKER_IMAGE_NAME: ghcr.io/kguardian-dev/kguardian/broker 6 | IMAGE_VERSION: local 7 | 8 | tasks: 9 | all: 10 | desc: "Run all broker tasks" 11 | cmds: 12 | - task: build 13 | 14 | build: 15 | desc: "Build and load the broker Docker image" 16 | cmds: 17 | - docker build -t {{.BROKER_IMAGE_NAME}}:{{.IMAGE_VERSION}} broker -f broker/Dockerfile 18 | #- docker push {{.BROKER_IMAGE_NAME}}:{{.IMAGE_VERSION}} 19 | - kind load docker-image {{.BROKER_IMAGE_NAME}}:{{.IMAGE_VERSION}} 20 | 21 | preflight: 22 | desc: "Run preflight checks for Broker" 23 | cmds: 24 | - command -v docker || echo "docker is not installed" 25 | - command -v kind || echo "kind is not installed" 26 | -------------------------------------------------------------------------------- /broker/db/install.md: -------------------------------------------------------------------------------- 1 | 2 | # Prerequiste 3 | 4 | sudo apt-get install libpq-dev 5 | cargo install diesel_cli --no-default-features --features postgres 6 | 7 | # local db set up first time 8 | docker run \ 9 | --name postgres-db \ 10 | -p 5432:5432 \ 11 | -e POSTGRES_USER=rust \ 12 | -e POSTGRES_HOST_AUTH_METHOD=trust \ 13 | -e POSTGRES_DB=kube \ 14 | -d postgres 15 | 16 | echo DATABASE_URL=postgres://rust@localhost/kube >.env 17 | 18 | diesel setup 19 | 20 | diesel migration generate pod_traffic 21 | 22 | diesel print-schema > src/schema.rs 23 | 24 | diesel_ext --model > src/models.rs 25 | 26 | # Copy the sql statement to newly generated watcher folder in <>_pod-traffic 27 | 28 | diesel migration run --config-file diesel.toml 29 | 30 | ## Helpful sql commands 31 | ``` 32 | sudo psql postgres://rust@localhost/kube 33 | \dt 34 | ``` 35 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2022", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "types": ["vite/client"], 9 | "skipLibCheck": true, 10 | 11 | /* Bundler mode */ 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "moduleDetection": "force", 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "erasableSyntaxOnly": true, 24 | "noFallthroughCasesInSwitch": true, 25 | "noUncheckedSideEffectImports": true 26 | }, 27 | "include": ["src"] 28 | } 29 | -------------------------------------------------------------------------------- /controller/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DEBIAN_VERSION="12.9" 2 | 3 | FROM --platform=$BUILDPLATFORM ubuntu:24.04 AS builder 4 | 5 | # Set the working directory to /artifacts 6 | WORKDIR /artifacts 7 | 8 | # Create the subdirectories for amd64 and arm64 in a single RUN command 9 | RUN mkdir -p linux/amd64 linux/arm64 10 | 11 | COPY linux/amd64/kguardian linux/amd64/ 12 | COPY linux/arm64/kguardian linux/arm64/ 13 | 14 | FROM debian:${DEBIAN_VERSION}-slim 15 | 16 | ARG TARGETPLATFORM 17 | 18 | ENV DEBIAN_FRONTEND=noninteractive 19 | 20 | # RUN rm /var/lib/dpkg/info/libc-bin.* 21 | # RUN apt-get clean && apt-get update && apt-get install libc-bin -y 22 | RUN apt-get update && apt-get install -y util-linux iproute2 libelf-dev 23 | 24 | COPY --from=builder --chown=root:root --chmod=0755 /artifacts/$TARGETPLATFORM/kguardian /usr/local/bin 25 | 26 | ENTRYPOINT ["/usr/local/bin/kguardian"] 27 | -------------------------------------------------------------------------------- /frontend/src/hooks/useNamespaces.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { apiClient } from '../services/api'; 3 | 4 | export const useNamespaces = () => { 5 | const [namespaces, setNamespaces] = useState(['default']); 6 | const [loading, setLoading] = useState(true); 7 | 8 | useEffect(() => { 9 | const fetchNamespaces = async () => { 10 | setLoading(true); 11 | try { 12 | const ns = await apiClient.getNamespaces(); 13 | if (ns.length > 0) { 14 | setNamespaces(ns); 15 | } 16 | } catch (error) { 17 | console.error('Error fetching namespaces:', error); 18 | // Keep default namespace on error 19 | } finally { 20 | setLoading(false); 21 | } 22 | }; 23 | 24 | fetchNamespaces(); 25 | }, []); 26 | 27 | return { namespaces, loading }; 28 | }; 29 | -------------------------------------------------------------------------------- /mcp-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kguardian-mcp-server", 3 | "version": "1.2.1", 4 | "description": "Model Context Protocol server for kguardian", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "bin": { 8 | "kguardian-mcp": "dist/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "dev": "tsx src/index.ts", 13 | "start": "node dist/index.js", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "keywords": [ 17 | "mcp", 18 | "kubernetes", 19 | "security", 20 | "kguardian" 21 | ], 22 | "author": "kguardian-dev", 23 | "license": "BUSL-1.1", 24 | "dependencies": { 25 | "@modelcontextprotocol/sdk": "^1.0.4", 26 | "axios": "^1.7.9", 27 | "zod": "^4.0.0" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^24.0.0", 31 | "tsx": "^4.19.2", 32 | "typescript": "^5.7.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/constants/ui.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * UI Constants 3 | * 4 | * Centralized constants for UI dimensions, timing, and constraints 5 | */ 6 | 7 | // Layout dimensions 8 | export const UI_DIMENSIONS = { 9 | // Header and footer 10 | HEADER_HEIGHT: 73, 11 | FOOTER_HEIGHT: 34, 12 | 13 | // AI Assistant Panel 14 | AI_PANEL_DEFAULT_WIDTH: 448, // max-w-md 15 | AI_PANEL_COLLAPSED_WIDTH: 48, 16 | AI_PANEL_MIN_WIDTH: 300, 17 | AI_PANEL_MAX_WIDTH_RATIO: 0.8, // 80% of window width 18 | 19 | // Data Table 20 | TABLE_DEFAULT_HEIGHT: 320, // h-80 21 | TABLE_MIN_HEIGHT: 100, 22 | TABLE_MAX_HEIGHT_RATIO: 0.8, // 80% of available height 23 | } as const; 24 | 25 | // Animation durations (in milliseconds) 26 | export const UI_TIMING = { 27 | RESIZE_DEBOUNCE: 100, 28 | FIT_VIEW_DELAY: 100, 29 | FIT_VIEW_DURATION: 400, 30 | TRANSITION_DURATION: 300, 31 | } as const; 32 | 33 | // Z-index layers 34 | export const Z_INDEX = { 35 | BACKDROP: 40, 36 | MODAL: 50, 37 | SIDE_PANEL: 50, 38 | TOOLTIP: 60, 39 | } as const; 40 | -------------------------------------------------------------------------------- /docs/api-reference/endpoints/syscalls.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Syscall Endpoints" 3 | description: "Retrieve syscall observations" 4 | --- 5 | 6 | ## POST /pods/syscalls 7 | 8 | Add syscall observation data. 9 | 10 | ### Request 11 | 12 | ```json 13 | { 14 | "pod_name": "my-app", 15 | "pod_namespace": "production", 16 | "syscalls": ["read", "write", "open", "close", "socket"], 17 | "arch": "x86_64" 18 | } 19 | ``` 20 | 21 | --- 22 | 23 | ## GET /pod/syscalls/name/:namespace/:pod_name 24 | 25 | Get observed syscalls for a pod. 26 | 27 | ### Example 28 | 29 | ```bash 30 | curl http://localhost:9090/pod/syscalls/name/production/my-app 31 | ``` 32 | 33 | ### Response 34 | 35 | ```json 36 | { 37 | "pod_name": "my-app", 38 | "pod_namespace": "production", 39 | "syscalls": [ 40 | "accept", 41 | "bind", 42 | "brk", 43 | "close", 44 | "connect", 45 | "listen", 46 | "mmap", 47 | "munmap", 48 | "open", 49 | "read", 50 | "socket", 51 | "write" 52 | ], 53 | "arch": "x86_64" 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /frontend/src/types/networkPolicy.ts: -------------------------------------------------------------------------------- 1 | export interface NetworkPolicyPort { 2 | protocol: string; 3 | port: string | number; 4 | } 5 | 6 | export interface PodSelector { 7 | matchLabels: Record; 8 | } 9 | 10 | export interface NamespaceSelector { 11 | matchLabels: Record; 12 | } 13 | 14 | export interface IPBlock { 15 | cidr: string; 16 | except?: string[]; 17 | } 18 | 19 | export interface NetworkPolicyPeer { 20 | podSelector?: PodSelector; 21 | namespaceSelector?: NamespaceSelector; 22 | ipBlock?: IPBlock; 23 | } 24 | 25 | export interface NetworkPolicyRule { 26 | id: string; 27 | peers: NetworkPolicyPeer[]; 28 | ports: NetworkPolicyPort[]; 29 | } 30 | 31 | export interface NetworkPolicy { 32 | apiVersion: string; 33 | kind: string; 34 | metadata: { 35 | name: string; 36 | namespace: string; 37 | }; 38 | spec: { 39 | podSelector: PodSelector; 40 | policyTypes: string[]; 41 | ingress?: NetworkPolicyRule[]; 42 | egress?: NetworkPolicyRule[]; 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /docs/essentials/code.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Code blocks' 3 | description: 'Display inline code and code blocks' 4 | icon: 'code' 5 | --- 6 | 7 | ## Inline code 8 | 9 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 10 | 11 | ``` 12 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 13 | ``` 14 | 15 | ## Code blocks 16 | 17 | Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language. 18 | 19 | ```java HelloWorld.java 20 | class HelloWorld { 21 | public static void main(String[] args) { 22 | System.out.println("Hello, World!"); 23 | } 24 | } 25 | ``` 26 | 27 | ````md 28 | ```java HelloWorld.java 29 | class HelloWorld { 30 | public static void main(String[] args) { 31 | System.out.println("Hello, World!"); 32 | } 33 | } 34 | ``` 35 | ```` 36 | -------------------------------------------------------------------------------- /mcp-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.25-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | # Install build dependencies 6 | RUN apk add --no-cache git ca-certificates 7 | 8 | # Copy go module files 9 | COPY go.mod go.sum ./ 10 | 11 | # Download dependencies 12 | RUN go mod download 13 | 14 | # Copy source code 15 | COPY main.go ./ 16 | COPY tools ./tools 17 | 18 | # Build the binary 19 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o kguardian-mcp . 20 | 21 | # Production stage 22 | FROM alpine:latest 23 | 24 | WORKDIR /app 25 | 26 | # Install ca-certificates for HTTPS requests 27 | RUN apk --no-cache add ca-certificates 28 | 29 | # Copy built application from builder 30 | COPY --from=builder /app/kguardian-mcp . 31 | 32 | # Set environment variables 33 | ENV BROKER_URL=http://broker.kguardian.svc.cluster.local:9090 34 | ENV PORT=8081 35 | 36 | # Create non-root user 37 | RUN addgroup -g 1000 mcp && \ 38 | adduser -D -u 1000 -G mcp mcp 39 | 40 | # Run as non-root user 41 | USER mcp 42 | 43 | # Expose port 44 | EXPOSE 8081 45 | 46 | # Start the MCP server 47 | CMD ["./kguardian-mcp"] 48 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "private": true, 4 | "version": "1.6.1", 5 | "type": "module", 6 | "license": "BUSL-1.1", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc -b && vite build", 10 | "lint": "eslint .", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "axios": "^1.12.2", 15 | "lucide-react": "^0.562.0", 16 | "react": "^19.1.1", 17 | "react-dom": "^19.1.1", 18 | "reactflow": "^11.11.4" 19 | }, 20 | "devDependencies": { 21 | "@eslint/js": "^9.36.0", 22 | "@tailwindcss/postcss": "^4.1.16", 23 | "@types/node": "^24.6.0", 24 | "@types/react": "^19.1.16", 25 | "@types/react-dom": "^19.1.9", 26 | "@vitejs/plugin-react": "^5.0.4", 27 | "autoprefixer": "^10.4.21", 28 | "eslint": "^9.36.0", 29 | "eslint-plugin-react-hooks": "^7.0.0", 30 | "eslint-plugin-react-refresh": "^0.4.22", 31 | "globals": "^16.4.0", 32 | "postcss": "^8.5.6", 33 | "tailwindcss": "^4.1.16", 34 | "typescript": "~5.9.3", 35 | "typescript-eslint": "^8.45.0", 36 | "vite": "^7.1.7" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /broker/db/migrations/2023-06-06-070736_pod_traffic/up.sql: -------------------------------------------------------------------------------- 1 | -- Your SQL goes here 2 | CREATE TABLE pod_traffic ( 3 | uuid VARCHAR PRIMARY KEY, 4 | pod_name VARCHAR, 5 | pod_namespace VARCHAR, 6 | pod_ip VARCHAR, 7 | pod_port VARCHAR, 8 | ip_protocol VARCHAR, 9 | traffic_type VARCHAR, 10 | traffic_in_out_ip VARCHAR, 11 | traffic_in_out_port VARCHAR, 12 | decision VARCHAR, 13 | time_stamp TIMESTAMP NOT NULL 14 | ); 15 | 16 | 17 | -- Your SQL goes here 18 | CREATE TABLE pod_details ( 19 | pod_name VARCHAR PRIMARY KEY, 20 | pod_ip VARCHAR, 21 | pod_namespace VARCHAR, 22 | pod_obj JSON, 23 | time_stamp TIMESTAMP NOT NULL, 24 | node_name VARCHAR NOT NULL, 25 | is_dead BOOLEAN NOT NULL DEFAULT FALSE 26 | ); 27 | 28 | 29 | CREATE TABLE svc_details ( 30 | svc_ip VARCHAR PRIMARY KEY, 31 | svc_name VARCHAR, 32 | svc_namespace VARCHAR, 33 | service_spec JSON, 34 | time_stamp TIMESTAMP NOT NULL 35 | ); 36 | 37 | 38 | CREATE TABLE pod_syscalls ( 39 | pod_name VARCHAR PRIMARY KEY, 40 | pod_namespace VARCHAR, 41 | syscalls VARCHAR, 42 | arch VARCHAR, 43 | time_stamp TIMESTAMP NOT NULL 44 | ); 45 | 46 | -------------------------------------------------------------------------------- /llm-bridge/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:24-alpine AS builder 2 | 3 | WORKDIR /app 4 | 5 | # Copy package files and TypeScript config 6 | COPY package*.json tsconfig.json ./ 7 | 8 | # Install dependencies 9 | RUN npm ci 10 | 11 | # Copy source code 12 | COPY src ./src 13 | 14 | # Build TypeScript 15 | RUN npm run build 16 | 17 | # Production stage 18 | FROM node:24-alpine 19 | 20 | WORKDIR /app 21 | 22 | # Copy package files and install production dependencies only 23 | COPY package*.json ./ 24 | RUN npm ci --only=production 25 | 26 | # Copy built application from builder 27 | COPY --from=builder /app/dist ./dist 28 | 29 | # Set environment variables 30 | ENV NODE_ENV=production 31 | ENV PORT=8080 32 | ENV BROKER_URL=http://broker.kguardian.svc.cluster.local:9090 33 | 34 | # Expose port 35 | EXPOSE 8080 36 | 37 | # Run as non-root user 38 | USER node 39 | 40 | # Health check 41 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 42 | CMD node -e "require('http').get('http://localhost:8080/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" 43 | 44 | # Start the server 45 | CMD ["node", "dist/index.js"] 46 | -------------------------------------------------------------------------------- /charts/kguardian/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kguardian 3 | description: Kubernetes network traffic & security monitoring using eBPF 4 | home: https://github.com/kguardian-dev/kguardian 5 | 6 | # This is the chart version. This version number should be incremented each time you make changes 7 | # to the chart and its templates, including the app version. 8 | # Versions are expected to follow Semantic Versioning (https://semver.org/) 9 | version: 1.7.0 10 | 11 | # This is the version number of the application being deployed. This version number should be 12 | # incremented each time you make changes to the application. Versions are not expected to 13 | # follow Semantic Versioning. They should reflect the version the application is using. 14 | # It is recommended to use it with quotes. 15 | appVersion: "1.0.0" 16 | kubeVersion: ">= 1.18.0-0" 17 | 18 | keywords: 19 | - BPF 20 | - eBPF 21 | - Kubernetes 22 | - Networking 23 | - Security 24 | - Observability 25 | - Troubleshooting 26 | 27 | sources: 28 | - https://github.com/kguardian-dev/kguardian 29 | 30 | maintainers: 31 | - name: maheshrayas 32 | - name: xunholy 33 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Mintlify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /frontend/src/components/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Moon, Sun } from 'lucide-react'; 3 | import { useTheme } from '../contexts/ThemeContext'; 4 | 5 | const ThemeToggle: React.FC = () => { 6 | const { theme, toggleTheme } = useTheme(); 7 | 8 | return ( 9 | 29 | ); 30 | }; 31 | 32 | export default ThemeToggle; 33 | -------------------------------------------------------------------------------- /controller/src/log.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | pub fn init_logger() { 4 | // check the rust log 5 | if env::var("RUST_LOG").is_err() { 6 | env::set_var("RUST_LOG", "info") 7 | } 8 | if std::env::var("RUST_LOG").unwrap().to_lowercase().eq("info") { 9 | std::env::set_var("RUST_LOG", "info,kube_client=off"); 10 | } else { 11 | std::env::set_var( 12 | "RUST_LOG", 13 | "debug,kube_client=off,tower=off,hyper=off,h2=off,rustls=off,reqwest=off", 14 | ); 15 | } 16 | 17 | let timer = time::format_description::parse( 18 | "[year]-[month padding:zero]-[day padding:zero] [hour]:[minute]:[second]", 19 | ) 20 | .expect("Time Error"); 21 | let time_offset = time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC); 22 | let timer = tracing_subscriber::fmt::time::OffsetTime::new(time_offset, timer); 23 | 24 | // Initialize the logger 25 | tracing_subscriber::fmt() 26 | .with_writer(std::io::stderr) 27 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 28 | .with_timer(timer) 29 | .init(); 30 | } 31 | -------------------------------------------------------------------------------- /docs/api-reference/endpoints/services.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Service Endpoints" 3 | description: "Service IP to metadata mapping" 4 | --- 5 | 6 | ## POST /svc/spec 7 | 8 | Add or update service metadata. 9 | 10 | ### Request 11 | 12 | ```json 13 | { 14 | "svc_ip": "10.96.0.100", 15 | "svc_name": "my-service", 16 | "svc_namespace": "production", 17 | "service_spec": { 18 | "metadata": { /* service metadata */ }, 19 | "spec": { 20 | "selector": { 21 | "app": "my-app" 22 | }, 23 | "ports": [ 24 | { 25 | "port": 80, 26 | "targetPort": 8080 27 | } 28 | ] 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | --- 35 | 36 | ## GET /svc/ip/:ip 37 | 38 | Retrieve service details by cluster IP. 39 | 40 | ### Example 41 | 42 | ```bash 43 | curl http://localhost:9090/svc/ip/10.96.0.100 44 | ``` 45 | 46 | ### Response 47 | 48 | ```json 49 | { 50 | "svc_ip": "10.96.0.100", 51 | "svc_name": "my-service", 52 | "svc_namespace": "production", 53 | "service_spec": { 54 | "spec": { 55 | "selector": { 56 | "app": "my-app" 57 | } 58 | } 59 | } 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /controller/src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::Error; 2 | use serde_json::Value; 3 | use std::env; 4 | use tracing::debug; 5 | 6 | use lazy_static::lazy_static; 7 | 8 | lazy_static! { 9 | static ref CLIENT: reqwest::Client = reqwest::Client::new(); 10 | } 11 | 12 | pub(crate) async fn api_post_call(v: Value, path: &str) -> Result<(), Error> { 13 | let api_endpoint = env::var("API_ENDPOINT") 14 | .map_err(|_| Error::Custom("API_ENDPOINT environment variable not set".to_string()))?; 15 | let url = format!("{}/{}", api_endpoint, path); 16 | 17 | debug!("Posting to {}", url); 18 | 19 | // Serialize to bytes directly without intermediate string allocation 20 | let json_bytes = serde_json::to_vec(&v) 21 | .map_err(|e| Error::Custom(format!("Failed to serialize JSON: {}", e)))?; 22 | 23 | let res = CLIENT 24 | .post(&url) 25 | .header("content-type", "application/json") 26 | .body(json_bytes) 27 | .send() 28 | .await 29 | .map_err(|e| Error::ApiError(format!("{}", e)))?; 30 | 31 | debug!("Post url {} : Success", url); 32 | debug!("Post call response {:?}", res); 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /llm-bridge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llm-bridge", 3 | "version": "1.1.1", 4 | "description": "LLM bridge service for kguardian - connects frontend to LLM providers", 5 | "type": "module", 6 | "main": "dist/index.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "dev": "tsx watch src/index.ts", 10 | "start": "node dist/index.js", 11 | "typecheck": "tsc --noEmit", 12 | "lint": "eslint src --ext .ts" 13 | }, 14 | "keywords": [ 15 | "llm", 16 | "kubernetes", 17 | "security", 18 | "kguardian", 19 | "ai" 20 | ], 21 | "author": "kguardian-dev", 22 | "license": "BUSL-1.1", 23 | "dependencies": { 24 | "@modelcontextprotocol/sdk": "^1.23.0", 25 | "axios": "^1.7.9", 26 | "cors": "^2.8.5", 27 | "dotenv": "^17.0.0", 28 | "express": "^5.0.0", 29 | "zod": "^4.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/cors": "^2.8.17", 33 | "@types/express": "^5.0.0", 34 | "@types/node": "^24.0.0", 35 | "@typescript-eslint/eslint-plugin": "^8.19.1", 36 | "@typescript-eslint/parser": "^8.19.1", 37 | "eslint": "^9.18.0", 38 | "tsx": "^4.19.2", 39 | "typescript": "^5.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /broker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "broker" 3 | version = "1.5.0" #:version 4 | authors = ["Mahesh Rayas"] 5 | description = """ 6 | API server for kguardian - stores telemetry data from eBPF controller 7 | """ 8 | documentation = "https://github.com/kguardian-dev/kguardian" 9 | homepage = "https://github.com/kguardian-dev/kguardian" 10 | repository = "https://github.com/kguardian-dev/kguardian" 11 | readme = "README.md" 12 | keywords = ["kubernetes", "k8s"] 13 | license = "BUSL-1.1" 14 | edition = "2021" 15 | 16 | [[bin]] 17 | name = "broker" 18 | path = "src/main.rs" 19 | 20 | [lib] 21 | name = "api" 22 | path = "src/lib.rs" 23 | bench = false 24 | 25 | [dependencies] 26 | dotenv = "0.15" 27 | actix-web = "4.11" 28 | actix-cors = "0.7" 29 | diesel = { version = "2.2.6", features = ["postgres","chrono","serde_json","r2d2"] } 30 | serde = { version = "1.0.228", features = ["derive"] } 31 | serde_json = "1.0.143" 32 | uuid = { version = "1.18.0", features = ["v4", "serde"] } 33 | thiserror = "2.0.16" 34 | tracing = {version = "0.1", features = ['log']} 35 | chrono = { version = "0.4.41", features = ["serde"] } 36 | diesel_migrations = "2.1.0" 37 | tracing-subscriber = { version = "0.3.12", features = ["json", "env-filter"] } -------------------------------------------------------------------------------- /.taskfiles/UI/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | vars: 6 | UI_IMAGE_NAME: ghcr.io/kguardian-dev/kguardian/guardian-ui 7 | IMAGE_VERSION: local 8 | 9 | tasks: 10 | all: 11 | desc: "Run all UI tasks" 12 | cmds: 13 | - task: build 14 | 15 | install: 16 | desc: "Install UI dependencies" 17 | dir: ui 18 | cmds: 19 | - npm install 20 | 21 | dev: 22 | desc: "Start UI development server" 23 | dir: ui 24 | cmds: 25 | - npm run dev 26 | 27 | build: 28 | desc: "Build and package the UI" 29 | dir: ui 30 | cmds: 31 | - npm run build 32 | 33 | docker: 34 | desc: "Build UI Docker image and load into Kind" 35 | deps: [build] 36 | cmds: 37 | - docker build -t {{.UI_IMAGE_NAME}}:{{.IMAGE_VERSION}} ui 38 | - kind load docker-image {{.UI_IMAGE_NAME}}:{{.IMAGE_VERSION}} 39 | 40 | lint: 41 | desc: "Lint UI code" 42 | dir: ui 43 | cmds: 44 | - npm run lint 45 | 46 | preflight: 47 | desc: "Run preflight checks for UI" 48 | cmds: 49 | - command -v node || echo "Node.js is not installed" 50 | - command -v npm || echo "npm is not installed" 51 | -------------------------------------------------------------------------------- /advisor/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22.3-alpine3.18 as build 2 | 3 | # Buildx build-in ARGs 4 | ARG TARGETOS 5 | ARG TARGETARCH 6 | ARG TARGETVARIANT="" 7 | # Additional build ARGs passed from --build-args 8 | ARG APPLICATION_NAME="advisor" 9 | ARG VERSION 10 | ARG SHA 11 | 12 | # Environment variables used at compile time by Golang 13 | ENV GO111MODULE=on \ 14 | CGO_ENABLED=0 \ 15 | GOOS=${TARGETOS} \ 16 | GOARCH=${TARGETARCH} \ 17 | GOARM=${TARGETVARIANT} 18 | 19 | WORKDIR /go/src/github.com/kguardian-dev/kguardian/advisor/ 20 | 21 | COPY go.mod go.sum ./ 22 | 23 | RUN go mod download 24 | 25 | COPY . . 26 | 27 | RUN go build -a -installsuffix cgo \ 28 | -ldflags="-w -extldflags '-static' \ 29 | -X 'main.ApplicationName=${APPLICATION_NAME}}' \ 30 | -X 'main.Version=${VERSION}' -X 'main.SHA=${SHA}' \ 31 | -X 'github.com/kguardian-dev/kguardian/advisor/pkg/k8s.Version=${VERSION}'" \ 32 | -o advisor . 33 | 34 | FROM gcr.io/distroless/static:nonroot 35 | 36 | ARG LOG_INFO 37 | ARG PORT 38 | 39 | ENV LOG_INFO=${LOG_INFO} \ 40 | PORT=${PORT} 41 | 42 | WORKDIR / 43 | 44 | COPY --from=build --chown=nonroot /go/src/github.com/kguardian-dev/kguardian/advisor/advisor . 45 | 46 | USER nonroot:nonroot 47 | 48 | ENTRYPOINT ["/advisor"] 49 | -------------------------------------------------------------------------------- /broker/src/error.rs: -------------------------------------------------------------------------------- 1 | use actix_web::error::BlockingError; 2 | use diesel::r2d2; 3 | 4 | /// All errors possible to occur during reconciliation 5 | #[derive(Debug, thiserror::Error)] 6 | pub enum Error { 7 | /// Error in user input or typically missing fields. 8 | #[error("Invalid User Input: {0}")] 9 | UserInputError(String), 10 | 11 | /// Any error originating from the `diesel` crate 12 | #[error("DieselResult Error: {source}")] 13 | SQLError { 14 | #[from] 15 | source: diesel::result::Error, 16 | }, 17 | /// Any error originating from the `actix` crate 18 | #[error("Actix Web Error: {source}")] 19 | ActixWebError { 20 | #[from] 21 | source: actix_web::Error, 22 | }, 23 | 24 | /// Any error originating from the `kube-rs` crate 25 | #[error("BlockingError: {source}")] 26 | BlockingError { 27 | #[from] 28 | source: BlockingError, 29 | }, 30 | /// Any error originating from the `diesel` crate 31 | #[error("SQL Error: {source}")] 32 | R2D2Error { 33 | #[from] 34 | source: r2d2::Error, 35 | }, 36 | } 37 | 38 | impl From for Error { 39 | fn from(s: String) -> Self { 40 | Error::UserInputError(s) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build 2 | FROM node:24-alpine AS builder 3 | 4 | WORKDIR /app 5 | 6 | # Copy package files 7 | COPY package*.json ./ 8 | 9 | # Install dependencies 10 | RUN npm ci 11 | 12 | # Copy source code 13 | COPY . . 14 | 15 | # Build the application for production 16 | RUN npm run build 17 | 18 | # Stage 2: Production with Vite preview 19 | FROM node:24-alpine 20 | 21 | WORKDIR /app 22 | 23 | # Create non-root user with UID 1337 to match Helm chart security context 24 | RUN adduser -D -u 1337 -s /bin/sh appuser && \ 25 | chown -R 1337:1337 /app 26 | 27 | # Copy package files and built assets from builder stage 28 | COPY --from=builder --chown=1337:1337 /app/package*.json ./ 29 | COPY --from=builder --chown=1337:1337 /app/dist ./dist 30 | COPY --from=builder --chown=1337:1337 /app/node_modules ./node_modules 31 | COPY --from=builder --chown=1337:1337 /app/vite.config.ts ./ 32 | 33 | # Expose port 5173 34 | EXPOSE 5173 35 | 36 | # Health check 37 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ 38 | CMD wget --quiet --tries=1 --spider http://localhost:5173/ || exit 1 39 | 40 | # Switch to non-root user 41 | USER 1337 42 | 43 | # Use vite preview for production 44 | CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "5173"] 45 | -------------------------------------------------------------------------------- /.slsa-goreleaser/advisor/linux-amd64.yml: -------------------------------------------------------------------------------- 1 | # Version for this file. 2 | version: 1 3 | 4 | # (Optional) List of env variables used during compilation. 5 | env: 6 | - GO111MODULE=on 7 | - CGO_ENABLED=0 8 | 9 | # (Optional) Flags for the compiler. 10 | flags: 11 | - -trimpath 12 | - -tags=netgo 13 | 14 | # The OS to compile for. `GOOS` env variable will be set to this value. 15 | goos: linux 16 | 17 | # The architecture to compile for. `GOARCH` env variable will be set to this value. 18 | goarch: amd64 19 | 20 | # (Optional) Entrypoint to compile. 21 | # main: ./path/to/main.go 22 | 23 | # (Optional) Working directory. (default: root of the project) 24 | dir: ./advisor 25 | 26 | # Binary output name. 27 | # {{ .Os }} will be replaced by goos field in the config file. 28 | # {{ .Arch }} will be replaced by goarch field in the config file. 29 | binary: advisor-{{ .Os }}-{{ .Arch }} 30 | 31 | # (Optional) ldflags generated dynamically in the workflow, and set as the `evaluated-envs` input variables in the workflow. 32 | ldflags: 33 | - "-X main.Version={{ .Env.VERSION }}" 34 | - "-X main.Commit={{ .Env.COMMIT }}" 35 | - "-X main.CommitDate={{ .Env.COMMIT_DATE }}" 36 | - "-X main.TreeState={{ .Env.TREE_STATE }}" 37 | - "-X github.com/kguardian-dev/kguardian/advisor/pkg/k8s.Version={{ .Env.VERSION }}" 38 | -------------------------------------------------------------------------------- /.slsa-goreleaser/advisor/linux-arm64.yml: -------------------------------------------------------------------------------- 1 | # Version for this file. 2 | version: 1 3 | 4 | # (Optional) List of env variables used during compilation. 5 | env: 6 | - GO111MODULE=on 7 | - CGO_ENABLED=0 8 | 9 | # (Optional) Flags for the compiler. 10 | flags: 11 | - -trimpath 12 | - -tags=netgo 13 | 14 | # The OS to compile for. `GOOS` env variable will be set to this value. 15 | goos: linux 16 | 17 | # The architecture to compile for. `GOARCH` env variable will be set to this value. 18 | goarch: arm64 19 | 20 | # (Optional) Entrypoint to compile. 21 | # main: ./path/to/main.go 22 | 23 | # (Optional) Working directory. (default: root of the project) 24 | dir: ./advisor 25 | 26 | # Binary output name. 27 | # {{ .Os }} will be replaced by goos field in the config file. 28 | # {{ .Arch }} will be replaced by goarch field in the config file. 29 | binary: advisor-{{ .Os }}-{{ .Arch }} 30 | 31 | # (Optional) ldflags generated dynamically in the workflow, and set as the `evaluated-envs` input variables in the workflow. 32 | ldflags: 33 | - "-X main.Version={{ .Env.VERSION }}" 34 | - "-X main.Commit={{ .Env.COMMIT }}" 35 | - "-X main.CommitDate={{ .Env.COMMIT_DATE }}" 36 | - "-X main.TreeState={{ .Env.TREE_STATE }}" 37 | - "-X github.com/kguardian-dev/kguardian/advisor/pkg/k8s.Version={{ .Env.VERSION }}" 38 | -------------------------------------------------------------------------------- /.slsa-goreleaser/advisor/darwin-amd64.yml: -------------------------------------------------------------------------------- 1 | # Version for this file. 2 | version: 1 3 | 4 | # (Optional) List of env variables used during compilation. 5 | env: 6 | - GO111MODULE=on 7 | - CGO_ENABLED=0 8 | 9 | # (Optional) Flags for the compiler. 10 | flags: 11 | - -trimpath 12 | - -tags=netgo 13 | 14 | # The OS to compile for. `GOOS` env variable will be set to this value. 15 | goos: darwin 16 | 17 | # The architecture to compile for. `GOARCH` env variable will be set to this value. 18 | goarch: amd64 19 | 20 | # (Optional) Entrypoint to compile. 21 | # main: ./path/to/main.go 22 | 23 | # (Optional) Working directory. (default: root of the project) 24 | dir: ./advisor 25 | 26 | # Binary output name. 27 | # {{ .Os }} will be replaced by goos field in the config file. 28 | # {{ .Arch }} will be replaced by goarch field in the config file. 29 | binary: advisor-{{ .Os }}-{{ .Arch }} 30 | 31 | # (Optional) ldflags generated dynamically in the workflow, and set as the `evaluated-envs` input variables in the workflow. 32 | ldflags: 33 | - "-X main.Version={{ .Env.VERSION }}" 34 | - "-X main.Commit={{ .Env.COMMIT }}" 35 | - "-X main.CommitDate={{ .Env.COMMIT_DATE }}" 36 | - "-X main.TreeState={{ .Env.TREE_STATE }}" 37 | - "-X github.com/kguardian-dev/kguardian/advisor/pkg/k8s.Version={{ .Env.VERSION }}" 38 | -------------------------------------------------------------------------------- /.slsa-goreleaser/advisor/darwin-arm64.yml: -------------------------------------------------------------------------------- 1 | # Version for this file. 2 | version: 1 3 | 4 | # (Optional) List of env variables used during compilation. 5 | env: 6 | - GO111MODULE=on 7 | - CGO_ENABLED=0 8 | 9 | # (Optional) Flags for the compiler. 10 | flags: 11 | - -trimpath 12 | - -tags=netgo 13 | 14 | # The OS to compile for. `GOOS` env variable will be set to this value. 15 | goos: darwin 16 | 17 | # The architecture to compile for. `GOARCH` env variable will be set to this value. 18 | goarch: arm64 19 | 20 | # (Optional) Entrypoint to compile. 21 | # main: ./path/to/main.go 22 | 23 | # (Optional) Working directory. (default: root of the project) 24 | dir: ./advisor 25 | 26 | # Binary output name. 27 | # {{ .Os }} will be replaced by goos field in the config file. 28 | # {{ .Arch }} will be replaced by goarch field in the config file. 29 | binary: advisor-{{ .Os }}-{{ .Arch }} 30 | 31 | # (Optional) ldflags generated dynamically in the workflow, and set as the `evaluated-envs` input variables in the workflow. 32 | ldflags: 33 | - "-X main.Version={{ .Env.VERSION }}" 34 | - "-X main.Commit={{ .Env.COMMIT }}" 35 | - "-X main.CommitDate={{ .Env.COMMIT_DATE }}" 36 | - "-X main.TreeState={{ .Env.TREE_STATE }}" 37 | - "-X github.com/kguardian-dev/kguardian/advisor/pkg/k8s.Version={{ .Env.VERSION }}" 38 | -------------------------------------------------------------------------------- /.taskfiles/Controller/Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | vars: 6 | CONTROLLER_IMAGE_NAME: ghcr.io/kguardian-dev/kguardian/controller 7 | IMAGE_VERSION: local 8 | TARGET: x86_64-unknown-linux-gnu 9 | 10 | tasks: 11 | all: 12 | desc: "Run all controller tasks" 13 | cmds: 14 | - task: build 15 | 16 | build: 17 | desc: "Build the controller with Cargo and cross" 18 | cmds: 19 | - cd controller && cross build --release --target {{.TARGET}} 20 | - mkdir -p localbin 21 | - cp controller/target/{{.TARGET}}/release/kguardian localbin 22 | - docker build -t {{.CONTROLLER_IMAGE_NAME}}:{{.IMAGE_VERSION}} . -f controller/Dockerfile 23 | #- docker push {{.CONTROLLER_IMAGE_NAME}}:{{.IMAGE_VERSION}} 24 | - kind load docker-image {{.CONTROLLER_IMAGE_NAME}}:{{.IMAGE_VERSION}} 25 | - rm -rf localbin 26 | 27 | preflight: 28 | desc: "Run preflight checks for Controller" 29 | cmds: 30 | - command -v cargo || echo "cargo is not installed" 31 | - command -v cross || echo "cross is not installed" 32 | - command -v docker || echo "docker is not installed" 33 | - command -v kind || echo "kind is not installed" 34 | - command -v helm || echo "helm is not installed" 35 | -------------------------------------------------------------------------------- /docs/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/api-reference/endpoints/traffic.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Traffic Endpoints" 3 | description: "Query network traffic data" 4 | --- 5 | 6 | ## GET /pod/traffic/:pod_ip 7 | 8 | Get all observed traffic for a pod by IP address. 9 | 10 | ### Example 11 | 12 | ```bash 13 | curl http://localhost:9090/pod/traffic/10.244.1.5 14 | ``` 15 | 16 | ### Response 17 | 18 | ```json 19 | [ 20 | { 21 | "uuid": "123e4567-e89b-12d3-a456-426614174000", 22 | "pod_name": "my-app", 23 | "pod_ip": "10.244.1.5", 24 | "pod_namespace": "production", 25 | "pod_port": "8080", 26 | "traffic_type": "INGRESS", 27 | "traffic_in_out_ip": "10.244.2.10", 28 | "traffic_in_out_port": "52341", 29 | "ip_protocol": "TCP" 30 | }, 31 | { 32 | "uuid": "234e5678-e89b-12d3-a456-426614174001", 33 | "pod_name": "my-app", 34 | "pod_ip": "10.244.1.5", 35 | "pod_namespace": "production", 36 | "pod_port": "35821", 37 | "traffic_type": "EGRESS", 38 | "traffic_in_out_ip": "10.244.3.15", 39 | "traffic_in_out_port": "5432", 40 | "ip_protocol": "TCP" 41 | } 42 | ] 43 | ``` 44 | 45 | --- 46 | 47 | ## GET /pod/traffic/name/:namespace/:pod_name 48 | 49 | Get traffic by pod name and namespace. 50 | 51 | ### Example 52 | 53 | ```bash 54 | curl http://localhost:9090/pod/traffic/name/production/my-app 55 | ``` 56 | -------------------------------------------------------------------------------- /.github/workflows/charts-docs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: "Charts: Update README" 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | paths: 10 | - '.github/workflows/charts-readme-docs.yaml' 11 | - 'charts/**' 12 | 13 | env: 14 | HELM_DOCS_VERSION: 1.11.2 15 | 16 | jobs: 17 | chart-readme: 18 | name: Update README 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 23 | with: 24 | ref: ${{ github.event.pull_request.head.ref }} 25 | 26 | - name: install helm-docs 27 | run: | 28 | cd /tmp 29 | wget https://github.com/norwoodj/helm-docs/releases/download/v${{env.HELM_DOCS_VERSION}}/helm-docs_Linux_x86_64.tar.gz 30 | tar -xvf helm-docs_Linux_x86_64.tar.gz 31 | sudo mv helm-docs /usr/local/sbin 32 | 33 | - name: run helm-docs 34 | run: | 35 | helm-docs -t README.md.gotmpl -o README.md 36 | 37 | - name: Commit changes 38 | uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7 39 | id: auto-commit 40 | with: 41 | branch: ${{ github.event.pull_request.head.ref }} 42 | -------------------------------------------------------------------------------- /broker/db/migrations/00000000000000_diesel_initial_setup/up.sql: -------------------------------------------------------------------------------- 1 | -- This file was automatically created by Diesel to setup helper functions 2 | -- and other internal bookkeeping. This file is safe to edit, any future 3 | -- changes will be added to existing projects as new migrations. 4 | 5 | 6 | 7 | 8 | -- Sets up a trigger for the given table to automatically set a column called 9 | -- `updated_at` whenever the row is modified (unless `updated_at` was included 10 | -- in the modified columns) 11 | -- 12 | -- # Example 13 | -- 14 | -- ```sql 15 | -- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); 16 | -- 17 | -- SELECT diesel_manage_updated_at('users'); 18 | -- ``` 19 | CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ 20 | BEGIN 21 | EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s 22 | FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); 23 | END; 24 | $$ LANGUAGE plpgsql; 25 | 26 | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ 27 | BEGIN 28 | IF ( 29 | NEW IS DISTINCT FROM OLD AND 30 | NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at 31 | ) THEN 32 | NEW.updated_at := current_timestamp; 33 | END IF; 34 | RETURN NEW; 35 | END; 36 | $$ LANGUAGE plpgsql; 37 | -------------------------------------------------------------------------------- /llm-bridge/src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | // LLM Provider types 4 | export enum LLMProvider { 5 | OPENAI = "openai", 6 | ANTHROPIC = "anthropic", 7 | GEMINI = "gemini", 8 | COPILOT = "copilot", 9 | } 10 | 11 | // Message history 12 | export const MessageSchema = z.object({ 13 | role: z.enum(['user', 'assistant', 'system']), 14 | content: z.string(), 15 | }); 16 | 17 | export type Message = z.infer; 18 | 19 | // Request/Response schemas 20 | export const ChatRequestSchema = z.object({ 21 | message: z.string().min(1), 22 | provider: z.nativeEnum(LLMProvider).optional(), 23 | model: z.string().optional(), 24 | conversationId: z.string().optional(), 25 | systemPrompt: z.string().optional(), 26 | history: z.array(MessageSchema).optional(), 27 | }); 28 | 29 | export type ChatRequest = z.infer; 30 | 31 | export interface ChatResponse { 32 | message: string; 33 | provider: LLMProvider; 34 | model: string; 35 | conversationId?: string; 36 | } 37 | 38 | export interface ErrorResponse { 39 | error: string; 40 | details?: string; 41 | } 42 | 43 | // Broker API tool definitions 44 | export interface ToolCall { 45 | name: string; 46 | arguments: Record; 47 | } 48 | 49 | export interface ToolResult { 50 | data: any; 51 | error?: string; 52 | } 53 | -------------------------------------------------------------------------------- /controller/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum Error { 5 | #[error("Kubernetes reported error: {source}")] 6 | KubeError { 7 | #[from] 8 | source: kube::Error, 9 | }, 10 | #[error("Kubernetes Watcher runtime error: {source}")] 11 | KubeWatcherError { 12 | #[from] 13 | source: kube::runtime::watcher::Error, 14 | }, 15 | #[error("Finalizer Error: {0}")] 16 | // NB: awkward type because finalizer::Error embeds the reconciler error (which is this) 17 | // so boxing this error to break cycles 18 | FinalizerError(#[source] Box>), 19 | 20 | #[error("IO Error: {source}")] 21 | IOError { 22 | #[from] 23 | source: std::io::Error, 24 | }, 25 | 26 | #[error("IllegalDocument")] 27 | IllegalDocument, 28 | 29 | #[error("ApiError - {0}")] 30 | ApiError(String), 31 | 32 | #[error("Custom error: {0}")] 33 | Custom(String), 34 | 35 | #[error("Tokio Join error: {source}")] 36 | JoinError { 37 | #[from] 38 | source: tokio::task::JoinError, 39 | }, 40 | } 41 | 42 | pub type Result = std::result::Result; 43 | 44 | impl Error { 45 | pub fn metric_label(&self) -> String { 46 | format!("{self:?}").to_lowercase() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/components/NamespaceSelector.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layers } from 'lucide-react'; 3 | 4 | interface NamespaceSelectorProps { 5 | selectedNamespace: string; 6 | onNamespaceChange: (namespace: string) => void; 7 | namespaces?: string[]; 8 | } 9 | 10 | const NamespaceSelector: React.FC = ({ 11 | selectedNamespace, 12 | onNamespaceChange, 13 | namespaces = ['default'], 14 | }) => { 15 | return ( 16 |
17 | 18 | 21 | 35 |
36 | ); 37 | }; 38 | 39 | export default NamespaceSelector; 40 | -------------------------------------------------------------------------------- /controller/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kguardian" 3 | version = "1.6.0" 4 | license = "BUSL-1.1" 5 | edition = "2021" 6 | 7 | [build-dependencies] 8 | libbpf-cargo = "0.25.0" 9 | vmlinux = { version = "0.0", git = "https://github.com/libbpf/vmlinux.h.git", rev = "8f91e9fd5b488ff57074e589e3960940f3387830" } 10 | 11 | [dependencies] 12 | anyhow = "1.0.96" 13 | libbpf-rs = "0.25.0" 14 | libbpf-sys = "1.5.1" 15 | plain = "0.2" 16 | time = { version = "0.3", features = ["formatting", "local-offset", "macros"]} 17 | tokio = { version = "1.48", features = ["macros", "rt-multi-thread", "fs", "time"] } 18 | kube = { version = "2.0.0", features = ["runtime", "derive"] } 19 | k8s-openapi = { version = "0.26", features = ["latest"] } 20 | futures = "0.3.31" 21 | thiserror = "2.0.17" 22 | tracing = "0.1.37" 23 | serde = "1.0.228" 24 | serde_derive = "1.0.228" 25 | serde_json = "1.0.143" 26 | tracing-subscriber = { version = "0.3.16", features = ["json", "env-filter", "local-time"] } 27 | containerd-client = "0.6.0" 28 | regex = "1.11.2" 29 | procfs = "0.18.0" 30 | reqwest = { version = "0.12.23", features = ["json"] } 31 | uuid = { version = "1.18.0", features = ["v4"]} 32 | chrono = { version = "0.4.41", features = ["serde"] } 33 | openssl = { version = "0.10.73", features = ["vendored"] } 34 | lazy_static = "1.5.0" 35 | libseccomp = "0.4" 36 | moka = { version = "0.12.10", features = ["future"]} 37 | dashmap = "6.1" 38 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/api-reference/endpoints/pods.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Pod Endpoints" 3 | description: "Add and retrieve pod metadata" 4 | --- 5 | 6 | ## POST /pods 7 | 8 | Add or update pod metadata. 9 | 10 | ### Request 11 | 12 | ```json 13 | { 14 | "pod_ip": "10.244.1.5", 15 | "pod_name": "my-app-7d9f6b8c4-x5z2w", 16 | "pod_namespace": "production" 17 | } 18 | ``` 19 | 20 | ### Response 21 | 22 | ```json 23 | { 24 | "uuid": "550e8400-e29b-41d4-a716-446655440000", 25 | "pod_ip": "10.244.1.5", 26 | "pod_name": "my-app-7d9f6b8c4-x5z2w", 27 | "pod_namespace": "production" 28 | } 29 | ``` 30 | 31 | --- 32 | 33 | ## POST /pod/spec 34 | 35 | Add full pod specification including labels and metadata. 36 | 37 | ### Request 38 | 39 | ```json 40 | { 41 | "pod_ip": "10.244.1.5", 42 | "pod_name": "my-app", 43 | "pod_namespace": "production", 44 | "pod_obj": { 45 | "metadata": { 46 | "labels": { 47 | "app": "my-app", 48 | "version": "v1.0.0" 49 | } 50 | }, 51 | "spec": { /* full pod spec */ } 52 | } 53 | } 54 | ``` 55 | 56 | --- 57 | 58 | ## GET /pod/ip/:ip 59 | 60 | Retrieve pod details by IP address. 61 | 62 | ### Example 63 | 64 | ```bash 65 | curl http://localhost:9090/pod/ip/10.244.1.5 66 | ``` 67 | 68 | ### Response 69 | 70 | ```json 71 | { 72 | "uuid": "550e8400-e29b-41d4-a716-446655440000", 73 | "pod_ip": "10.244.1.5", 74 | "pod_name": "my-app", 75 | "pod_namespace": "production", 76 | "pod_obj": { /* full pod spec */ } 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Mintlify Starter Kit 2 | 3 | Use the starter kit to get your docs deployed and ready to customize. 4 | 5 | Click the green **Use this template** button at the top of this repo to copy the Mintlify starter kit. The starter kit contains examples with 6 | 7 | - Guide pages 8 | - Navigation 9 | - Customizations 10 | - API reference pages 11 | - Use of popular components 12 | 13 | **[Follow the full quickstart guide](https://starter.mintlify.com/quickstart)** 14 | 15 | ## Development 16 | 17 | Install the [Mintlify CLI](https://www.npmjs.com/package/mint) to preview your documentation changes locally. To install, use the following command: 18 | 19 | ``` 20 | npm i -g mint 21 | ``` 22 | 23 | Run the following command at the root of your documentation, where your `docs.json` is located: 24 | 25 | ``` 26 | mint dev 27 | ``` 28 | 29 | View your local preview at `http://localhost:3000`. 30 | 31 | ## Publishing changes 32 | 33 | Install our GitHub app from your [dashboard](https://dashboard.mintlify.com/settings/organization/github-app) to propagate changes from your repo to your deployment. Changes are deployed to production automatically after pushing to the default branch. 34 | 35 | ## Need help? 36 | 37 | ### Troubleshooting 38 | 39 | - If your dev environment isn't running: Run `mint update` to ensure you have the most recent version of the CLI. 40 | - If a page loads as a 404: Make sure you are running in a folder with a valid `docs.json`. 41 | 42 | ### Resources 43 | - [Mintlify documentation](https://mintlify.com/docs) 44 | -------------------------------------------------------------------------------- /docs/cli/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "CLI Overview" 3 | description: "kubectl kguardian command reference" 4 | icon: "terminal" 5 | --- 6 | 7 | ## kubectl kguardian 8 | 9 | The kguardian CLI is a kubectl plugin that generates security policies from observed runtime behavior. 10 | 11 | ## Installation 12 | 13 | See the [Installation Guide](/installation#install-the-cli-plugin) for detailed instructions. 14 | 15 | ## Global Flags 16 | 17 | Available for all commands: 18 | 19 | | Flag | Description | Default | 20 | |------|-------------|---------| 21 | | `--kubeconfig` | Path to kubeconfig file | `$KUBECONFIG` or `~/.kube/config` | 22 | | `--context` | Kubernetes context to use | Current context | 23 | | `-n, --namespace` | Namespace scope | Current namespace | 24 | | `--debug` | Enable debug logging | `false` | 25 | 26 | ## Commands 27 | 28 | 29 | 30 | Generate Network Policies from observed traffic 31 | 32 | 33 | Generate Seccomp profiles from syscall usage 34 | 35 | 36 | 37 | ## Examples 38 | 39 | ```bash 40 | # Generate network policy for a pod 41 | kubectl kguardian gen networkpolicy my-app -n production 42 | 43 | # Generate seccomp for all pods in namespace 44 | kubectl kguardian gen seccomp --all -n staging 45 | 46 | # Generate Cilium policies cluster-wide 47 | kubectl kguardian gen netpol -A --type cilium 48 | ``` 49 | -------------------------------------------------------------------------------- /advisor/pkg/k8s/labels_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | api "github.com/kguardian-dev/kguardian/advisor/pkg/api" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | func TestDetectSelectorLabels(t *testing.T) { 14 | clientset := &kubernetes.Clientset{} 15 | pod := &v1.Pod{ 16 | ObjectMeta: metav1.ObjectMeta{ 17 | Labels: map[string]string{ 18 | "app": "test-app", 19 | }, 20 | }, 21 | } 22 | podDetail := &api.PodDetail{ 23 | Pod: v1.Pod{ 24 | ObjectMeta: metav1.ObjectMeta{ 25 | Labels: map[string]string{ 26 | "app": "test-app", 27 | }, 28 | }, 29 | }, 30 | } 31 | serviceDetail := &api.SvcDetail{ 32 | Service: v1.Service{ 33 | Spec: v1.ServiceSpec{ 34 | Selector: map[string]string{ 35 | "app": "test-app", 36 | }, 37 | }, 38 | }, 39 | } 40 | 41 | labels1, err1 := detectSelectorLabels(clientset, pod) 42 | assert.NoError(t, err1) 43 | assert.Equal(t, map[string]string{"app": "test-app"}, labels1) 44 | 45 | labels2, err2 := detectSelectorLabels(clientset, podDetail) 46 | assert.NoError(t, err2) 47 | assert.Equal(t, map[string]string{"app": "test-app"}, labels2) 48 | 49 | labels3, err3 := detectSelectorLabels(clientset, serviceDetail) 50 | assert.NoError(t, err3) 51 | assert.Equal(t, map[string]string{"app": "test-app"}, labels3) 52 | 53 | _, err4 := detectSelectorLabels(clientset, "unknown type") 54 | assert.Error(t, err4) 55 | } 56 | -------------------------------------------------------------------------------- /docs/cli/gen-networkpolicy.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "gen networkpolicy" 3 | description: "Generate Kubernetes or Cilium Network Policies" 4 | icon: "network-wired" 5 | --- 6 | 7 | ## Synopsis 8 | 9 | Generate Network Policies based on observed pod traffic. 10 | 11 | ```bash 12 | kubectl kguardian gen networkpolicy [POD_NAME] [flags] 13 | kubectl kguardian gen netpol [POD_NAME] [flags] # Alias 14 | ``` 15 | 16 | ## Flags 17 | 18 | | Flag | Type | Description | Default | 19 | |------|------|-------------|---------| 20 | | `-n, --namespace` | string | Namespace of the pod | Current namespace | 21 | | `-a, --all` | bool | Generate for all pods in namespace | `false` | 22 | | `-A, --all-namespaces` | bool | Generate for all pods cluster-wide | `false` | 23 | | `-t, --type` | string | Policy type: `kubernetes` or `cilium` | `kubernetes` | 24 | | `--output-dir` | string | Directory to save policies | `network-policies` | 25 | | `--dry-run` | bool | Generate without applying | `true` | 26 | 27 | ## Examples 28 | 29 | ```bash 30 | # Single pod 31 | kubectl kguardian gen networkpolicy my-app -n prod --output-dir ./policies 32 | 33 | # All pods in namespace 34 | kubectl kguardian gen netpol --all -n staging --output-dir ./staging-policies 35 | 36 | # Cilium policies cluster-wide 37 | kubectl kguardian gen netpol -A --type cilium --output-dir ./cilium 38 | 39 | # Generate and apply 40 | kubectl kguardian gen netpol my-app --dry-run=false 41 | ``` 42 | 43 | --- 44 | 45 | See [Generating Network Policies](/guides/generating-network-policies) for detailed usage. 46 | -------------------------------------------------------------------------------- /.github/workflows/charts-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: "Charts: Release to GHCR OCI" 4 | 5 | on: 6 | workflow_dispatch: {} 7 | push: 8 | tags: 9 | - "chart/v*" 10 | 11 | env: 12 | CHARTS_SRC_DIR: "kguardian" 13 | TARGET_REGISTRY: ghcr.io 14 | 15 | jobs: 16 | release-charts: 17 | name: Release Charts 18 | runs-on: ubuntu-latest 19 | permissions: write-all 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Configure Git 27 | run: | 28 | git config user.name "$GITHUB_ACTOR" 29 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 30 | 31 | - name: Login to GitHub Container Registry 32 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 33 | with: 34 | registry: ${{ env.TARGET_REGISTRY }} 35 | username: ${{ github.actor }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | 38 | - name: Install Helm 39 | uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 40 | 41 | - name: Publish Helm Chart to GHCR 42 | run: | 43 | helm package charts/${{ env.CHARTS_SRC_DIR }} 44 | helm push kguardian-*.tgz oci://${{ env.TARGET_REGISTRY }}/${{ github.repository_owner }}/charts 45 | env: 46 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 47 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { 4 | "type": "feat", 5 | "section": "Features" 6 | }, 7 | { 8 | "type": "fix", 9 | "section": "Bug Fixes" 10 | }, 11 | { 12 | "type": "perf", 13 | "section": "Performance Improvements" 14 | }, 15 | { 16 | "type": "revert", 17 | "section": "Reverts" 18 | }, 19 | { 20 | "type": "docs", 21 | "section": "Documentation" 22 | }, 23 | { 24 | "type": "refactor", 25 | "section": "Code Refactoring" 26 | }, 27 | { 28 | "type": "test", 29 | "section": "Tests", 30 | "hidden": true 31 | }, 32 | { 33 | "type": "build", 34 | "section": "Build System", 35 | "hidden": true 36 | }, 37 | { 38 | "type": "ci", 39 | "section": "Continuous Integration", 40 | "hidden": true 41 | }, 42 | { 43 | "type": "chore", 44 | "section": "Miscellaneous Chores", 45 | "hidden": true 46 | }, 47 | { 48 | "type": "style", 49 | "section": "Styles", 50 | "hidden": true 51 | } 52 | ], 53 | "commitUrlFormat": "https://github.com/kguardian-dev/kguardian/commit/{{hash}}", 54 | "compareUrlFormat": "https://github.com/kguardian-dev/kguardian/compare/{{previousTag}}...{{currentTag}}", 55 | "issueUrlFormat": "https://github.com/kguardian-dev/kguardian/issues/{{id}}", 56 | "userUrlFormat": "https://github.com/{{user}}", 57 | "releaseCommitMessageFormat": "chore(release): {{currentTag}}", 58 | "issuePrefixes": ["#"] 59 | } 60 | -------------------------------------------------------------------------------- /docs/concepts/seccomp-profiles.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Seccomp Profiles" 3 | description: "Understanding seccomp and how kguardian generates syscall allowlists" 4 | icon: "shield-check" 5 | --- 6 | 7 | ## What is Seccomp? 8 | 9 | Seccomp (Secure Computing Mode) is a Linux kernel feature that restricts which **system calls** (syscalls) a process can make. 10 | 11 | ### Why Limit Syscalls? 12 | 13 | Most applications use only 50-100 of Linux's 300+ syscalls. Blocking unused syscalls: 14 | - Reduces attack surface 15 | - Prevents privilege escalation exploits 16 | - Stops malicious code from using dangerous syscalls 17 | 18 | ## How kguardian Generates Profiles 19 | 20 | 1. **Observes** all syscalls made by the container via eBPF 21 | 2. **Aggregates** unique syscall names over observation period 22 | 3. **Generates** JSON profile with allowlist 23 | 24 | Example generated profile: 25 | 26 | ```json 27 | { 28 | "defaultAction": "SCMP_ACT_ERRNO", 29 | "architectures": ["SCMP_ARCH_X86_64"], 30 | "syscalls": [ 31 | { 32 | "names": ["read", "write", "open", "close", "socket", "connect"], 33 | "action": "SCMP_ACT_ALLOW" 34 | } 35 | ] 36 | } 37 | ``` 38 | 39 | ## Actions 40 | 41 | - `SCMP_ACT_ALLOW`: Allow the syscall 42 | - `SCMP_ACT_ERRNO`: Block with error (default for unlisted) 43 | - `SCMP_ACT_LOG`: Log the syscall but allow it 44 | - `SCMP_ACT_KILL`: Kill the process (most restrictive) 45 | 46 | --- 47 | 48 | **Next steps:** 49 | - [Generate Seccomp Profiles](/guides/generating-seccomp-profiles) 50 | - [CLI Reference](/cli/gen-seccomp) 51 | -------------------------------------------------------------------------------- /mcp-server/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var Log *logrus.Logger 10 | 11 | // Init initializes the global logger with the specified log level 12 | func Init(level string) { 13 | Log = logrus.New() 14 | 15 | // Set output to stdout 16 | Log.SetOutput(os.Stdout) 17 | 18 | // Use JSON formatter for structured logging 19 | Log.SetFormatter(&logrus.JSONFormatter{ 20 | TimestampFormat: "2006-01-02T15:04:05.000Z07:00", 21 | FieldMap: logrus.FieldMap{ 22 | logrus.FieldKeyTime: "timestamp", 23 | logrus.FieldKeyLevel: "level", 24 | logrus.FieldKeyMsg: "message", 25 | }, 26 | }) 27 | 28 | // Set log level 29 | Log.SetLevel(parseLogLevel(level)) 30 | } 31 | 32 | // parseLogLevel converts a string log level to logrus.Level 33 | func parseLogLevel(level string) logrus.Level { 34 | switch level { 35 | case "debug", "DEBUG": 36 | return logrus.DebugLevel 37 | case "info", "INFO": 38 | return logrus.InfoLevel 39 | case "warn", "WARN", "warning", "WARNING": 40 | return logrus.WarnLevel 41 | case "error", "ERROR": 42 | return logrus.ErrorLevel 43 | default: 44 | return logrus.InfoLevel 45 | } 46 | } 47 | 48 | // WithField creates a new logger entry with an additional field 49 | func WithField(key string, value interface{}) *logrus.Entry { 50 | return Log.WithField(key, value) 51 | } 52 | 53 | // WithFields creates a new logger entry with multiple additional fields 54 | func WithFields(fields logrus.Fields) *logrus.Entry { 55 | return Log.WithFields(fields) 56 | } 57 | -------------------------------------------------------------------------------- /Taskfile.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://taskfile.dev/schema.json 3 | version: '3' 4 | 5 | includes: 6 | advisor: .taskfiles/Advisor 7 | broker: .taskfiles/Broker 8 | controller: .taskfiles/Controller 9 | ui: .taskfiles/UI 10 | 11 | vars: 12 | IMAGE_VERSION: local 13 | 14 | tasks: 15 | all: 16 | # deps: [kind] 17 | desc: "Run all tasks" 18 | cmds: 19 | #- task: advisor:all 20 | - task: broker:all 21 | - task: controller:all 22 | 23 | # kind: 24 | # desc: Create fresh kind cluster 25 | # cmds: 26 | # - kind delete cluster || true 27 | # - kind create cluster 28 | 29 | 30 | install:oci: 31 | deps: [all] 32 | desc: "Install from OCI registry (production-style install)" 33 | cmds: 34 | - helm upgrade kguardian oci://ghcr.io/kguardian-dev/charts/kguardian 35 | --namespace kguardian --create-namespace --set controller.image.tag={{.IMAGE_VERSION}} 36 | --set broker.image.tag={{.IMAGE_VERSION}} 37 | --set controller.image.pullPolicy=IfNotPresent 38 | --set broker.image.pullPolicy=IfNotPresent 39 | --set controller.initContainer.image.pullPolicy=IfNotPresent 40 | 41 | preflight: 42 | desc: "Run preflight checks for all components" 43 | cmds: 44 | - task: advisor:preflight 45 | - task: broker:preflight 46 | - task: controller:preflight 47 | - task: ui:preflight 48 | 49 | preflight-all: 50 | desc: "Run preflight checks and then all tasks" 51 | cmds: 52 | - task: preflight 53 | - task: all 54 | -------------------------------------------------------------------------------- /docs/logo/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | kguardian 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/logo/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | kguardian 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /charts/kguardian/templates/frontend/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.frontend.ingress.enabled -}} 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: {{ include "kguardian.name" . }}-frontend 6 | labels: 7 | {{- include "kguardian.labels" . | nindent 4 }} 8 | {{- with .Values.frontend.ingress.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | spec: 13 | {{- if .Values.frontend.ingress.className }} 14 | ingressClassName: {{ .Values.frontend.ingress.className }} 15 | {{- end }} 16 | {{- if .Values.frontend.ingress.tls }} 17 | tls: 18 | {{- range .Values.frontend.ingress.tls }} 19 | - hosts: 20 | {{- range .hosts }} 21 | - {{ . | quote }} 22 | {{- end }} 23 | secretName: {{ .secretName }} 24 | {{- end }} 25 | {{- end }} 26 | rules: 27 | {{- range .Values.frontend.ingress.hosts }} 28 | - host: {{ .host | quote }} 29 | http: 30 | paths: 31 | # Frontend static files 32 | - path: / 33 | pathType: Prefix 34 | backend: 35 | service: 36 | name: {{ $.Values.frontend.service.name }} 37 | port: 38 | number: {{ $.Values.frontend.service.port }} 39 | # Proxy /api requests to broker 40 | - path: /api 41 | pathType: Prefix 42 | backend: 43 | service: 44 | name: {{ $.Values.broker.service.name }} 45 | port: 46 | number: {{ $.Values.broker.service.port }} 47 | {{- end }} 48 | {{- end }} 49 | -------------------------------------------------------------------------------- /docs/cli/gen-seccomp.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "gen seccomp" 3 | description: "Generate Seccomp profiles from syscall observations" 4 | icon: "shield-check" 5 | --- 6 | 7 | ## Synopsis 8 | 9 | Generate Seccomp profiles based on observed syscall usage. 10 | 11 | ```bash 12 | kubectl kguardian gen seccomp [POD_NAME] [flags] 13 | kubectl kguardian gen secp [POD_NAME] [flags] # Alias 14 | ``` 15 | 16 | ## Flags 17 | 18 | | Flag | Type | Description | Default | 19 | |------|------|-------------|---------| 20 | | `-n, --namespace` | string | Namespace of the pod | Current namespace | 21 | | `-a, --all` | bool | Generate for all pods in namespace | `false` | 22 | | `-A, --all-namespaces` | bool | Generate for all pods cluster-wide | `false` | 23 | | `--output-dir` | string | Directory to save profiles | `seccomp-profiles` | 24 | | `--default-action` | string | Action for unlisted syscalls | `SCMP_ACT_ERRNO` | 25 | 26 | ## Default Actions 27 | 28 | - `SCMP_ACT_ERRNO` - Return error for unlisted syscalls (recommended) 29 | - `SCMP_ACT_LOG` - Log unlisted syscalls but allow them (audit mode) 30 | - `SCMP_ACT_KILL` - Kill process on unlisted syscall (strictest) 31 | 32 | ## Examples 33 | 34 | ```bash 35 | # Single pod 36 | kubectl kguardian gen seccomp my-app -n prod --output-dir ./seccomp 37 | 38 | # All pods with logging for unlisted 39 | kubectl kguardian gen secp --all -n staging --default-action SCMP_ACT_LOG 40 | 41 | # Cluster-wide with strict mode 42 | kubectl kguardian gen secp -A --default-action SCMP_ACT_KILL 43 | ``` 44 | 45 | --- 46 | 47 | See [Generating Seccomp Profiles](/guides/generating-seccomp-profiles) for detailed usage. 48 | -------------------------------------------------------------------------------- /frontend/src/types/index.ts: -------------------------------------------------------------------------------- 1 | // Matches broker's PodDetail type 2 | export interface PodInfo { 3 | pod_name: string; 4 | pod_ip: string; 5 | pod_namespace: string | null; 6 | pod_obj?: any; 7 | time_stamp: string; 8 | node_name: string; 9 | is_dead: boolean; 10 | pod_identity?: string | null; 11 | workload_selector_labels?: Record | null; 12 | } 13 | 14 | // Matches broker's PodTraffic type 15 | export interface NetworkTraffic { 16 | uuid: string; 17 | pod_name: string | null; 18 | pod_namespace: string | null; 19 | pod_ip: string | null; 20 | pod_port: string | null; 21 | ip_protocol: string | null; 22 | traffic_type: string | null; 23 | traffic_in_out_ip: string | null; 24 | traffic_in_out_port: string | null; 25 | decision: string | null; // ALLOW or DROP 26 | time_stamp: string; 27 | } 28 | 29 | // Matches broker's PodSyscalls type 30 | export interface SyscallInfo { 31 | pod_name: string; 32 | pod_namespace: string; 33 | syscalls: string; // Comma-separated string 34 | arch: string; 35 | time_stamp: string; 36 | } 37 | 38 | export interface PodNodeData { 39 | id: string; 40 | label: string; 41 | pod: PodInfo; // Primary pod (for backward compatibility and single-pod identities) 42 | pods: PodInfo[]; // All pods in this identity group 43 | traffic: NetworkTraffic[]; 44 | syscalls?: SyscallInfo[]; 45 | isExpanded: boolean; 46 | } 47 | 48 | // Matches broker's SvcDetail type 49 | export interface ServiceInfo { 50 | svc_ip: string; 51 | svc_name: string | null; 52 | svc_namespace: string | null; 53 | service_spec?: any; // Full Kubernetes Service object 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/contexts/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useEffect, useState } from 'react'; 2 | 3 | type Theme = 'light' | 'dark'; 4 | 5 | interface ThemeContextType { 6 | theme: Theme; 7 | toggleTheme: () => void; 8 | setTheme: (theme: Theme) => void; 9 | } 10 | 11 | const ThemeContext = createContext(undefined); 12 | 13 | export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { 14 | // Initialize theme from localStorage or default to dark 15 | const [theme, setThemeState] = useState(() => { 16 | const savedTheme = localStorage.getItem('kguardian-theme'); 17 | return (savedTheme === 'light' || savedTheme === 'dark') ? savedTheme : 'dark'; 18 | }); 19 | 20 | // Apply theme to document root 21 | useEffect(() => { 22 | const root = document.documentElement; 23 | root.classList.remove('light', 'dark'); 24 | root.classList.add(theme); 25 | localStorage.setItem('kguardian-theme', theme); 26 | }, [theme]); 27 | 28 | const toggleTheme = () => { 29 | setThemeState(prev => prev === 'dark' ? 'light' : 'dark'); 30 | }; 31 | 32 | const setTheme = (newTheme: Theme) => { 33 | setThemeState(newTheme); 34 | }; 35 | 36 | return ( 37 | 38 | {children} 39 | 40 | ); 41 | }; 42 | 43 | export const useTheme = () => { 44 | const context = useContext(ThemeContext); 45 | if (context === undefined) { 46 | throw new Error('useTheme must be used within a ThemeProvider'); 47 | } 48 | return context; 49 | }; 50 | -------------------------------------------------------------------------------- /docs/essentials/images.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Images and embeds' 3 | description: 'Add image, video, and other HTML elements' 4 | icon: 'image' 5 | --- 6 | 7 | 11 | 12 | ## Image 13 | 14 | ### Using Markdown 15 | 16 | The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code 17 | 18 | ```md 19 | ![title](/path/image.jpg) 20 | ``` 21 | 22 | Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed. 23 | 24 | ### Using embeds 25 | 26 | To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | ## Embeds and HTML elements 33 | 34 | 44 | 45 |
46 | 47 | 48 | 49 | Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility. 50 | 51 | 52 | 53 | ### iFrames 54 | 55 | Loads another HTML page within the document. Most commonly used for embedding videos. 56 | 57 | ```html 58 | 59 | ``` 60 | -------------------------------------------------------------------------------- /mcp-server/tools/cluster_pods.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // ClusterPodsInput defines the input parameters (no params needed) 15 | type ClusterPodsInput struct{} 16 | 17 | // ClusterPodsOutput defines the output structure 18 | type ClusterPodsOutput struct { 19 | Data string `json:"data" jsonschema:"All pod details in the cluster in JSON format"` 20 | } 21 | 22 | // ClusterPodsHandler handles the get_cluster_pods tool 23 | type ClusterPodsHandler struct { 24 | client *BrokerClient 25 | } 26 | 27 | // Call implements the tool handler 28 | func (h ClusterPodsHandler) Call( 29 | ctx context.Context, 30 | req *mcp.CallToolRequest, 31 | input ClusterPodsInput, 32 | ) (*mcp.CallToolResult, ClusterPodsOutput, error) { 33 | startTime := time.Now() 34 | logger.Log.Info("Received get_cluster_pods request") 35 | 36 | data, err := h.client.GetAllPods() 37 | if err != nil { 38 | logger.Log.WithFields(logrus.Fields{ 39 | "error": err.Error(), 40 | "total_duration": time.Since(startTime).String(), 41 | }).Error("Error fetching cluster pods") 42 | return nil, ClusterPodsOutput{}, fmt.Errorf("error fetching cluster pods: %w", err) 43 | } 44 | 45 | jsonData, err := json.MarshalIndent(data, "", " ") 46 | if err != nil { 47 | logger.Log.WithField("error", err.Error()).Error("Error marshaling response") 48 | return nil, ClusterPodsOutput{}, fmt.Errorf("error marshaling response: %w", err) 49 | } 50 | 51 | logger.Log.WithFields(logrus.Fields{ 52 | "response_bytes": len(jsonData), 53 | "total_duration": time.Since(startTime).String(), 54 | }).Info("Successfully fetched cluster pods") 55 | 56 | return nil, ClusterPodsOutput{Data: string(jsonData)}, nil 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/types/seccompProfile.ts: -------------------------------------------------------------------------------- 1 | export interface SeccompProfile { 2 | defaultAction: SeccompAction; 3 | architectures?: string[]; 4 | syscalls?: SeccompSyscall[]; 5 | } 6 | 7 | export interface SeccompSyscall { 8 | names: string[]; 9 | action: SeccompAction; 10 | } 11 | 12 | export type SeccompAction = 13 | | 'SCMP_ACT_ALLOW' 14 | | 'SCMP_ACT_ERRNO' 15 | | 'SCMP_ACT_KILL' 16 | | 'SCMP_ACT_KILL_PROCESS' 17 | | 'SCMP_ACT_KILL_THREAD' 18 | | 'SCMP_ACT_LOG' 19 | | 'SCMP_ACT_TRACE' 20 | | 'SCMP_ACT_TRAP'; 21 | 22 | export const SECCOMP_ACTIONS: SeccompAction[] = [ 23 | 'SCMP_ACT_ALLOW', 24 | 'SCMP_ACT_ERRNO', 25 | 'SCMP_ACT_KILL', 26 | 'SCMP_ACT_KILL_PROCESS', 27 | 'SCMP_ACT_KILL_THREAD', 28 | 'SCMP_ACT_LOG', 29 | 'SCMP_ACT_TRACE', 30 | 'SCMP_ACT_TRAP', 31 | ]; 32 | 33 | // Action descriptions from https://kubernetes.io/docs/reference/node/seccomp/ 34 | export const SECCOMP_ACTION_DESCRIPTIONS: Record = { 35 | 'SCMP_ACT_ALLOW': 'Allow the syscall to be executed', 36 | 'SCMP_ACT_ERRNO': 'Return an error code (reject syscall)', 37 | 'SCMP_ACT_KILL': 'Kill only the thread', 38 | 'SCMP_ACT_KILL_PROCESS': 'Kill the entire process', 39 | 'SCMP_ACT_KILL_THREAD': 'Kill only the thread', 40 | 'SCMP_ACT_LOG': 'Allow the syscall and log it to syslog or auditd', 41 | 'SCMP_ACT_TRACE': 'Notify a tracing process with the specified value', 42 | 'SCMP_ACT_TRAP': 'Throw a SIGSYS signal', 43 | }; 44 | 45 | export const ARCHITECTURES = [ 46 | 'SCMP_ARCH_X86_64', 47 | 'SCMP_ARCH_X86', 48 | 'SCMP_ARCH_X32', 49 | 'SCMP_ARCH_ARM', 50 | 'SCMP_ARCH_AARCH64', 51 | 'SCMP_ARCH_MIPS', 52 | 'SCMP_ARCH_MIPS64', 53 | 'SCMP_ARCH_MIPS64N32', 54 | 'SCMP_ARCH_MIPSEL', 55 | 'SCMP_ARCH_MIPSEL64', 56 | 'SCMP_ARCH_MIPSEL64N32', 57 | 'SCMP_ARCH_PPC', 58 | 'SCMP_ARCH_PPC64', 59 | 'SCMP_ARCH_PPC64LE', 60 | 'SCMP_ARCH_S390', 61 | 'SCMP_ARCH_S390X', 62 | ]; 63 | -------------------------------------------------------------------------------- /docs/concepts/ebpf-monitoring.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "eBPF Monitoring" 3 | description: "How kguardian uses eBPF for kernel-level observability" 4 | icon: "radar" 5 | --- 6 | 7 | ## What is eBPF? 8 | 9 | eBPF (extended Berkeley Packet Filter) is a revolutionary Linux kernel technology that allows running custom programs inside the kernel without changing kernel source code or loading kernel modules. 10 | 11 | ## How kguardian Uses eBPF 12 | 13 | kguardian attaches eBPF programs to kernel hooks to observe pod behavior: 14 | 15 | ### Network Traffic Monitoring 16 | 17 | **Hook points:** 18 | - `tcp_connect` - Outbound TCP connections 19 | - `tcp_sendmsg` / `tcp_recvmsg` - Data transmission 20 | - `udp_sendmsg` / `udp_recvmsg` - UDP traffic 21 | 22 | **Captured data:** 23 | - Source and destination IP addresses 24 | - Source and destination ports 25 | - Protocol (TCP/UDP) 26 | - Network namespace (to map to containers) 27 | 28 | ### Syscall Monitoring 29 | 30 | **Hook points:** 31 | - `sys_enter_*` - Entry to any syscall 32 | - `sys_exit_*` - Exit from syscall 33 | 34 | **Captured data:** 35 | - Syscall name (e.g., `open`, `read`, `socket`) 36 | - Process ID and container namespace 37 | - Architecture (x86_64, arm64, etc.) 38 | 39 | ## Why eBPF? 40 | 41 | 42 | 43 | ~1-2% CPU overhead vs 10-20% for proxy-based solutions 44 | 45 | 46 | Verifier ensures programs can't crash the kernel 47 | 48 | 49 | No code changes, sidecars, or pod restarts needed 50 | 51 | 52 | See everything, including encrypted connections 53 | 54 | 55 | 56 | --- 57 | 58 | **Learn more:** 59 | - [Architecture Overview](/architecture) 60 | - [Controller Implementation](/development/controller) 61 | -------------------------------------------------------------------------------- /.github/workflows/charts-lint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Lint and Test Charts 4 | 5 | on: 6 | pull_request: 7 | branches: 8 | - main 9 | paths: 10 | - '.github/workflows/charts-lint.yaml' 11 | - 'charts/**' 12 | 13 | jobs: 14 | lint-test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 19 | with: 20 | fetch-depth: 0 21 | 22 | - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 23 | with: 24 | python-version: '3.11' 25 | check-latest: true 26 | 27 | - name: Set up Helm 28 | uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 29 | with: 30 | version: v3.14.4 31 | 32 | - name: Set up chart-testing 33 | uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0 34 | 35 | - name: Run chart-testing (list-changed) 36 | id: list-changed 37 | run: | 38 | changed=$(ct list-changed --target-branch ${{ github.event.repository.default_branch }}) 39 | if [[ -n "$changed" ]]; then 40 | echo "changed=true" >> "$GITHUB_OUTPUT" 41 | fi 42 | 43 | - name: Run chart-testing (lint) 44 | if: steps.list-changed.outputs.changed == 'true' 45 | run: ct lint --config .github/ct.yaml --target-branch ${{ github.event.repository.default_branch }} 46 | 47 | - name: Create kind cluster 48 | if: steps.list-changed.outputs.changed == 'true' 49 | uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab # v1.13.0 50 | 51 | - name: Run chart-testing (install) 52 | if: steps.list-changed.outputs.changed == 'true' 53 | run: ct install --config .github/ct.yaml --target-branch ${{ github.event.repository.default_branch }} 54 | -------------------------------------------------------------------------------- /advisor/pkg/api/pod_syscall.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "github.com/rs/zerolog/log" 12 | ) 13 | 14 | type PodSysCall struct { 15 | Syscalls []string `json:"syscalls"` 16 | Arch string `json:"arch"` 17 | } 18 | 19 | type PodSysCallResponse struct { 20 | PodName string `json:"pod_name"` 21 | PodNamespace string `json:"pod_namespace"` 22 | Syscalls string `json:"syscalls"` 23 | Arch string `json:"arch"` 24 | } 25 | 26 | func GetPodSysCall(podName string) (PodSysCall, error) { 27 | time.Sleep(3 * time.Second) 28 | apiURL := "http://127.0.0.1:9090/pod/syscalls/" + podName 29 | 30 | resp, err := http.Get(apiURL) 31 | if err != nil { 32 | log.Error().Err(err).Msg("GetPodSysCall: Error making GET request") 33 | return PodSysCall{}, err 34 | } 35 | defer func() { 36 | if closeErr := resp.Body.Close(); closeErr != nil { 37 | log.Error().Err(closeErr).Msg("GetPodSysCall: Error closing response body") 38 | } 39 | }() 40 | 41 | if resp.StatusCode != http.StatusOK { 42 | return PodSysCall{}, fmt.Errorf("GetPodSysCall: received non-OK HTTP status code: %v", resp.StatusCode) 43 | } 44 | 45 | body, err := io.ReadAll(resp.Body) 46 | if err != nil { 47 | log.Error().Err(err).Msg("GetPodSysCall: Error reading response body") 48 | return PodSysCall{}, err 49 | } 50 | 51 | var podSysCallsResponse []PodSysCallResponse 52 | if err := json.Unmarshal(body, &podSysCallsResponse); err != nil { 53 | log.Error().Err(err).Msg("GetPodSysCall: Error unmarshalling JSON") 54 | return PodSysCall{}, err 55 | } 56 | 57 | if len(podSysCallsResponse) == 0 { 58 | return PodSysCall{}, fmt.Errorf("GetPodSysCall: No pod syscall found in database") 59 | } 60 | 61 | var podSysCalls PodSysCall 62 | 63 | podSysCalls.Syscalls = strings.Split(podSysCallsResponse[0].Syscalls, ",") 64 | podSysCalls.Arch = podSysCallsResponse[0].Arch 65 | 66 | return podSysCalls, nil 67 | } 68 | -------------------------------------------------------------------------------- /controller/src/bpf/syscall.bpf.c: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "helper.h" 7 | 8 | struct 9 | { 10 | __uint(type, BPF_MAP_TYPE_RINGBUF); 11 | __uint(max_entries, 128 * 1024); // 128KB ring buffer 12 | } syscall_events SEC(".maps"); 13 | 14 | struct data_t 15 | { 16 | __u64 inum; 17 | __u64 sysnbr; 18 | }; 19 | 20 | SEC("tracepoint/raw_syscalls/sys_enter") 21 | int trace_execve(struct trace_event_raw_sys_enter *ctx) 22 | { 23 | struct task_struct *task; 24 | u32 *inum = 0; 25 | 26 | task = (struct task_struct *)bpf_get_current_task(); 27 | __u64 net_ns = BPF_CORE_READ(task, nsproxy, net_ns, ns.inum); 28 | 29 | // Early exit if not in tracked namespace 30 | inum = bpf_map_lookup_elem(&inode_num, &net_ns); 31 | if (!inum) 32 | return 0; 33 | 34 | // Filter syscalls using allowlist if populated 35 | // If allowlist is empty (no entries), trace all syscalls (backward compatible) 36 | u32 syscall_id = (__u32)ctx->id; 37 | u32 *allowed = bpf_map_lookup_elem(&allowed_syscalls, &syscall_id); 38 | 39 | // If allowlist has entries, only trace allowed syscalls 40 | // Check if map is populated by testing a known syscall (0) 41 | u32 zero = 0; 42 | u32 *test = bpf_map_lookup_elem(&allowed_syscalls, &zero); 43 | 44 | // If allowlist is populated (test returns non-NULL) but current syscall not found, skip 45 | if (test && !allowed) 46 | return 0; 47 | 48 | // Reserve space in ring buffer 49 | struct data_t *data; 50 | data = bpf_ringbuf_reserve(&syscall_events, sizeof(*data), 0); 51 | if (!data) 52 | return 0; // Buffer full, drop event 53 | 54 | // Fill event data 55 | data->sysnbr = ctx->id; 56 | data->inum = net_ns; 57 | 58 | // Submit to userspace 59 | bpf_ringbuf_submit(data, 0); 60 | 61 | return 0; 62 | } 63 | 64 | char LICENSE[] SEC("license") = "GPL"; 65 | -------------------------------------------------------------------------------- /docs/concepts/network-policies.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Network Policies" 3 | description: "Understanding Kubernetes Network Policies and how kguardian generates them" 4 | icon: "network-wired" 5 | --- 6 | 7 | ## What are Network Policies? 8 | 9 | Kubernetes Network Policies are firewall rules for your pods. They control: 10 | - **Ingress**: What can connect TO your pod 11 | - **Egress**: What your pod can connect TO 12 | 13 | Without Network Policies, all pods can communicate with all other pods (flat network). 14 | 15 | ## Structure of a Network Policy 16 | 17 | ```yaml 18 | apiVersion: networking.k8s.io/v1 19 | kind: NetworkPolicy 20 | metadata: 21 | name: my-app 22 | spec: 23 | podSelector: # Which pods this policy applies to 24 | matchLabels: 25 | app: my-app 26 | policyTypes: 27 | - Ingress 28 | - Egress 29 | ingress: # Allow incoming from... 30 | - from: 31 | - podSelector: 32 | matchLabels: 33 | app: frontend 34 | ports: 35 | - port: 8080 36 | egress: # Allow outgoing to... 37 | - to: 38 | - podSelector: 39 | matchLabels: 40 | app: database 41 | ``` 42 | 43 | ## How kguardian Generates Policies 44 | 45 | 1. **Observes traffic** via eBPF for 5+ minutes 46 | 2. **Identifies peers** by resolving IPs to pods/services 47 | 3. **Groups rules** by protocol and port 48 | 4. **Deduplicates** to create minimal policies 49 | 5. **Generates YAML** ready to apply 50 | 51 | ## Default-Deny Strategy 52 | 53 | Best practice: Start with default-deny, then allowlist: 54 | 55 | ```yaml 56 | # 1. Deny all traffic 57 | apiVersion: networking.k8s.io/v1 58 | kind: NetworkPolicy 59 | metadata: 60 | name: default-deny 61 | spec: 62 | podSelector: {} 63 | policyTypes: 64 | - Ingress 65 | - Egress 66 | 67 | # 2. Apply kguardian-generated allowlist policies 68 | ``` 69 | 70 | --- 71 | 72 | **Next steps:** 73 | - [Generate Network Policies](/guides/generating-network-policies) 74 | - [Cilium Policies](/advanced/cilium-policies) 75 | -------------------------------------------------------------------------------- /charts/kguardian/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "kguardian.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "kguardian.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | This gets around an problem within helm discussed here 28 | https://github.com/helm/helm/issues/5358 29 | */}} 30 | {{- define "kguardian.namespace" -}} 31 | {{ .Values.namespace.name | default .Release.Namespace }} 32 | {{- end -}} 33 | 34 | {{/* 35 | Create chart name and version as used by the chart label. 36 | */}} 37 | {{- define "kguardian.chart" -}} 38 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 39 | {{- end }} 40 | 41 | {{/* 42 | Common labels 43 | */}} 44 | {{- define "kguardian.labels" -}} 45 | {{ include "kguardian.selectorLabels" . }} 46 | app.kubernetes.io/managed-by: {{ .Release.Service }} 47 | {{- if .Values.global.labels}} 48 | {{ toYaml .Values.global.labels }} 49 | {{- end }} 50 | {{- end }} 51 | 52 | {{/* 53 | Common Annotations 54 | */}} 55 | {{- define "kguardian.annotations" -}} 56 | {{- if .Values.global.annotations -}} 57 | {{- toYaml .Values.global.annotations | nindent 2 }} 58 | {{- end }} 59 | {{- end }} 60 | 61 | {{/* 62 | Selector labels 63 | */}} 64 | {{- define "kguardian.selectorLabels" -}} 65 | app.kubernetes.io/instance: {{ .Release.Name }} 66 | {{- end }} 67 | -------------------------------------------------------------------------------- /controller/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::ffi::OsStr; 3 | use std::path::PathBuf; 4 | 5 | use libbpf_cargo::SkeletonBuilder; 6 | use vmlinux; 7 | 8 | const SYSCALL_SRC: &str = "src/bpf/syscall.bpf.c"; 9 | const TCP_PROBE_SRC: &str = "src/bpf/network_probe.bpf.c"; 10 | const PACKET_DROP_SRC: &str = "src/bpf/netpolicy_drop.bpf.c"; 11 | 12 | fn main() { 13 | let out = PathBuf::from( 14 | env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set in build script"), 15 | ) 16 | .join("src") 17 | .join("bpf") 18 | .join("syscall.skel.rs"); 19 | 20 | let pkt_drop_out = PathBuf::from( 21 | env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set in build script"), 22 | ) 23 | .join("src") 24 | .join("bpf") 25 | .join("netpolicy_drop.skel.rs"); 26 | 27 | let tcp_probe_out = PathBuf::from( 28 | env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set in build script"), 29 | ) 30 | .join("src") 31 | .join("bpf") 32 | .join("network_probe.skel.rs"); 33 | 34 | let arch = env::var("CARGO_CFG_TARGET_ARCH") 35 | .expect("CARGO_CFG_TARGET_ARCH must be set in build script"); 36 | 37 | SkeletonBuilder::new() 38 | .source(SYSCALL_SRC) 39 | .clang_args([ 40 | OsStr::new("-I"), 41 | vmlinux::include_path_root().join(&arch).as_os_str(), 42 | ]) 43 | .build_and_generate(&out) 44 | .unwrap(); 45 | 46 | SkeletonBuilder::new() 47 | .source(TCP_PROBE_SRC) 48 | .clang_args([ 49 | OsStr::new("-I"), 50 | vmlinux::include_path_root().join(&arch).as_os_str(), 51 | ]) 52 | .build_and_generate(&tcp_probe_out) 53 | .unwrap(); 54 | 55 | SkeletonBuilder::new() 56 | .source(PACKET_DROP_SRC) 57 | .clang_args([ 58 | OsStr::new("-I"), 59 | vmlinux::include_path_root().join(arch).as_os_str(), 60 | ]) 61 | .build_and_generate(&pkt_drop_out) 62 | .unwrap(); 63 | 64 | println!("cargo:rerun-if-changed=src/bpf"); 65 | } 66 | -------------------------------------------------------------------------------- /docs/api-reference/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Broker API Reference" 3 | description: "REST API for querying kguardian telemetry data" 4 | icon: "code" 5 | --- 6 | 7 | ## Overview 8 | 9 | The kguardian Broker exposes a REST API for querying collected telemetry data. The CLI uses this API internally, but you can also integrate directly with it for custom workflows. 10 | 11 | ## Base URL 12 | 13 | ``` 14 | http://kguardian-broker.kguardian.svc.cluster.local:9090 15 | ``` 16 | 17 | For external access, use port-forwarding: 18 | 19 | ```bash 20 | kubectl port-forward -n kguardian svc/kguardian-broker 9090:9090 21 | ``` 22 | 23 | ## Authentication 24 | 25 | 26 | The current version (v1.0.0) has **no authentication**. The API should only be accessible within the cluster network. For production deployments, consider using NetworkPolicies, service mesh, or an API gateway with authentication. 27 | 28 | 29 | ## API Endpoints 30 | 31 | 32 | 33 | Add and retrieve pod metadata 34 | 35 | 36 | Query network traffic data 37 | 38 | 39 | Retrieve syscall observations 40 | 41 | 42 | Service IP to metadata mapping 43 | 44 | 45 | 46 | ## Response Format 47 | 48 | All responses are JSON with standard HTTP status codes: 49 | 50 | - `200 OK` - Success 51 | - `400 Bad Request` - Invalid parameters 52 | - `404 Not Found` - Resource not found 53 | - `500 Internal Server Error` - Server error 54 | 55 | ## Common Patterns 56 | 57 | ### Pagination 58 | 59 | Currently not supported. All matching records are returned. 60 | 61 | ### Filtering 62 | 63 | Some endpoints support filtering by namespace, pod name, or time range (coming soon). 64 | 65 | ### Rate Limiting 66 | 67 | No rate limiting is currently enforced. 68 | -------------------------------------------------------------------------------- /llm-bridge/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.1](https://github.com/kguardian-dev/kguardian/compare/llm-bridge/v1.1.0...llm-bridge/v1.1.1) (2025-12-12) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * llm with mcp ([0797192](https://github.com/kguardian-dev/kguardian/commit/079719225cabfdae169556af303a09c01d7e2243)) 9 | * llm with mcp ([453003f](https://github.com/kguardian-dev/kguardian/commit/453003ff9fbf2b00be1bb12d5c4f75b9f398727a)) 10 | 11 | ## [1.1.0](https://github.com/kguardian-dev/kguardian/compare/llm-bridge/v1.0.0...llm-bridge/v1.1.0) (2025-11-06) 12 | 13 | 14 | ### Features 15 | 16 | * add LLM + MCP ([0364874](https://github.com/kguardian-dev/kguardian/commit/03648744eabcf6005ff6a35cf761df608e239a81)) 17 | * add LLM + MCP integration ([a165a51](https://github.com/kguardian-dev/kguardian/commit/a165a5168ef91afe71bdb17e726baeb5df024511)) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * connect llm-bridge to MCP server for all 6 tools ([d0e8d5a](https://github.com/kguardian-dev/kguardian/commit/d0e8d5a588ea7ddc46700de3f2c7b27875aba5f8)) 23 | * **deps:** update dependency dotenv to v17 ([1f234d3](https://github.com/kguardian-dev/kguardian/commit/1f234d35873d01b7c828965d65d04979cfb82926)) 24 | * **deps:** update dependency dotenv to v17 ([def6bc7](https://github.com/kguardian-dev/kguardian/commit/def6bc7d92db00c8a29bd3700c1f914c0f918a43)) 25 | * **deps:** update dependency express to v5 ([a42ebe6](https://github.com/kguardian-dev/kguardian/commit/a42ebe65ff6d95cfd2c503fc23618aca31608260)) 26 | * **deps:** update dependency express to v5 ([a735730](https://github.com/kguardian-dev/kguardian/commit/a73573040ae9914ea9f3dbdb90e7266fa223d0c3)) 27 | * **deps:** update dependency zod to v4 ([29ac796](https://github.com/kguardian-dev/kguardian/commit/29ac79631a978fbf2434868f229f97c0efbec763)) 28 | * **deps:** update dependency zod to v4 ([7e71d16](https://github.com/kguardian-dev/kguardian/commit/7e71d160fced4cd46cfd0aeb0854ab3724169e57)) 29 | * docker builds ([0a449c8](https://github.com/kguardian-dev/kguardian/commit/0a449c859b93e839333955bcb6dd574042eaedc1)) 30 | * resolve OpenAI 400 error with proper tool message formatting ([b0e3adc](https://github.com/kguardian-dev/kguardian/commit/b0e3adcd1d4aad8078e74d87fd1d5bde9616a431)) 31 | -------------------------------------------------------------------------------- /docs/concepts/overview.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Core Concepts" 3 | description: "Understanding the fundamentals of kguardian's security approach" 4 | icon: "book-open" 5 | --- 6 | 7 | ## What is kguardian? 8 | 9 | kguardian implements **observed-based security** - it watches what your applications actually do at runtime and generates security policies that match that behavior. This is fundamentally different from traditional approaches where you manually write policies and hope they're correct. 10 | 11 | ## Key Concepts 12 | 13 | ### eBPF-Powered Observability 14 | 15 | kguardian uses eBPF (extended Berkeley Packet Filter) technology to observe kernel-level events: 16 | 17 | - **Network connections**: TCP/UDP socket operations 18 | - **System calls**: File access, process creation, etc. 19 | - **Zero code changes**: No sidecars, agents, or instrumentation needed 20 | 21 | 22 | eBPF runs safely in the kernel with minimal overhead (~1-2% CPU), making it perfect for production observability. 23 | 24 | 25 | ### Least-Privilege Security 26 | 27 | The core principle: **allow only what's actually used**. 28 | 29 | Instead of guessing what your app needs, kguardian: 30 | 1. Observes actual behavior 31 | 2. Generates policies that allow exactly that behavior 32 | 3. Blocks everything else (default-deny) 33 | 34 | ### Runtime to Policy Pipeline 35 | 36 | ```mermaid 37 | graph LR 38 | A[Application Runs] --> B[eBPF Observes] 39 | B --> C[Controller Enriches] 40 | C --> D[Broker Stores] 41 | D --> E[CLI Analyzes] 42 | E --> F[Policy Generated] 43 | ``` 44 | 45 | --- 46 | 47 | 48 | 49 | Deep dive into how eBPF observability works 50 | 51 | 52 | Understanding Kubernetes network policies 53 | 54 | 55 | What are seccomp profiles and why they matter 56 | 57 | 58 | See how all components work together 59 | 60 | 61 | -------------------------------------------------------------------------------- /broker/src/schema.rs: -------------------------------------------------------------------------------- 1 | // @generated automatically by Diesel CLI. 2 | 3 | diesel::table! { 4 | pod_details (pod_ip) { 5 | pod_name -> Varchar, 6 | pod_ip -> Varchar, 7 | pod_namespace -> Nullable, 8 | pod_obj -> Nullable, 9 | time_stamp -> Timestamp, 10 | node_name -> Varchar, 11 | is_dead -> Bool, 12 | pod_identity -> Nullable, 13 | workload_selector_labels -> Nullable, 14 | } 15 | } 16 | 17 | diesel::table! { 18 | pod_traffic (uuid) { 19 | uuid -> Varchar, 20 | pod_name -> Nullable, 21 | pod_namespace -> Nullable, 22 | pod_ip -> Nullable, 23 | pod_port -> Nullable, 24 | ip_protocol -> Nullable, 25 | traffic_type -> Nullable, 26 | traffic_in_out_ip -> Nullable, 27 | traffic_in_out_port -> Nullable, 28 | decision -> Nullable, 29 | time_stamp -> Timestamp, 30 | } 31 | } 32 | 33 | diesel::table! { 34 | pod_packet_drop (uuid) { 35 | uuid -> Varchar, 36 | pod_name -> Nullable, 37 | pod_namespace -> Nullable, 38 | pod_ip -> Nullable, 39 | pod_port -> Nullable, 40 | ip_protocol -> Nullable, 41 | traffic_type -> Nullable, 42 | traffic_in_out_ip -> Nullable, 43 | traffic_in_out_port -> Nullable, 44 | drop_reason -> Nullable, 45 | time_stamp -> Timestamp, 46 | } 47 | } 48 | 49 | diesel::table! { 50 | pod_syscalls (pod_name) { 51 | pod_name -> Varchar, 52 | pod_namespace -> Varchar, 53 | syscalls -> Varchar, 54 | arch -> Varchar, 55 | time_stamp -> Timestamp, 56 | } 57 | } 58 | 59 | diesel::table! { 60 | svc_details (svc_ip) { 61 | svc_ip -> Varchar, 62 | svc_name -> Nullable, 63 | svc_namespace -> Nullable, 64 | service_spec -> Nullable, 65 | time_stamp -> Timestamp, 66 | } 67 | } 68 | 69 | diesel::allow_tables_to_appear_in_same_query!(pod_details, pod_traffic, svc_details, pod_syscalls,); 70 | -------------------------------------------------------------------------------- /mcp-server/tools/pod_details.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // PodDetailsInput defines the input parameters for getting pod details by IP 15 | type PodDetailsInput struct { 16 | IP string `json:"ip" jsonschema:"The IP address of the pod to query"` 17 | } 18 | 19 | // PodDetailsOutput defines the output structure 20 | type PodDetailsOutput struct { 21 | Data string `json:"data" jsonschema:"Pod details in JSON format"` 22 | } 23 | 24 | // PodDetailsHandler handles the get_pod_details tool 25 | type PodDetailsHandler struct { 26 | client *BrokerClient 27 | } 28 | 29 | // Call implements the tool handler 30 | func (h PodDetailsHandler) Call( 31 | ctx context.Context, 32 | req *mcp.CallToolRequest, 33 | input PodDetailsInput, 34 | ) (*mcp.CallToolResult, PodDetailsOutput, error) { 35 | startTime := time.Now() 36 | logger.Log.WithField("ip", input.IP).Info("Received get_pod_details request") 37 | 38 | if input.IP == "" { 39 | logger.Log.Error("IP address is required but not provided") 40 | return nil, PodDetailsOutput{}, fmt.Errorf("IP address is required") 41 | } 42 | 43 | data, err := h.client.GetPodByIP(input.IP) 44 | if err != nil { 45 | logger.Log.WithFields(logrus.Fields{ 46 | "ip": input.IP, 47 | "error": err.Error(), 48 | "total_duration": time.Since(startTime).String(), 49 | }).Error("Error fetching pod details") 50 | return nil, PodDetailsOutput{}, fmt.Errorf("error fetching pod details: %w", err) 51 | } 52 | 53 | jsonData, err := json.MarshalIndent(data, "", " ") 54 | if err != nil { 55 | logger.Log.WithField("error", err.Error()).Error("Error marshaling response") 56 | return nil, PodDetailsOutput{}, fmt.Errorf("error marshaling response: %w", err) 57 | } 58 | 59 | logger.Log.WithFields(logrus.Fields{ 60 | "ip": input.IP, 61 | "response_bytes": len(jsonData), 62 | "total_duration": time.Since(startTime).String(), 63 | }).Info("Successfully fetched pod details") 64 | 65 | return nil, PodDetailsOutput{Data: string(jsonData)}, nil 66 | } 67 | -------------------------------------------------------------------------------- /advisor/pkg/k8s/portforward_test.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "k8s.io/client-go/kubernetes" 9 | "k8s.io/client-go/rest" 10 | ) 11 | 12 | func TestPortForward(t *testing.T) { 13 | // Test basic validation failures 14 | // Test nil config 15 | stopChan, errChan, done := PortForward(nil) 16 | select { 17 | case err := <-errChan: 18 | assert.Error(t, err) 19 | assert.Contains(t, err.Error(), "nil Kubernetes configuration") 20 | case <-time.After(time.Second): 21 | t.Fatal("Expected error but none received") 22 | } 23 | <-done // Wait for done signal 24 | close(stopChan) // Clean up 25 | 26 | // Test nil clientset 27 | nilClientConfig := &Config{ 28 | Clientset: nil, 29 | Config: &rest.Config{}, 30 | } 31 | stopChan, errChan, done = PortForward(nilClientConfig) 32 | select { 33 | case err := <-errChan: 34 | assert.Error(t, err) 35 | assert.Contains(t, err.Error(), "nil Kubernetes clientset") 36 | case <-time.After(time.Second): 37 | t.Fatal("Expected error but none received") 38 | } 39 | <-done // Wait for done signal 40 | close(stopChan) // Clean up 41 | 42 | // Test nil REST config 43 | nilRestConfig := &Config{ 44 | Clientset: &kubernetes.Clientset{}, 45 | Config: nil, 46 | } 47 | stopChan, errChan, done = PortForward(nilRestConfig) 48 | select { 49 | case err := <-errChan: 50 | assert.Error(t, err) 51 | assert.Contains(t, err.Error(), "nil REST configuration") 52 | case <-time.After(time.Second): 53 | t.Fatal("Expected error but none received") 54 | } 55 | <-done // Wait for done signal 56 | close(stopChan) // Clean up 57 | } 58 | 59 | func TestWriterFunc(t *testing.T) { 60 | // Test the writerFunc adapter 61 | var called bool 62 | var capturedData []byte 63 | 64 | // Create a writerFunc that captures the data 65 | w := writerFunc(func(p []byte) (int, error) { 66 | called = true 67 | capturedData = make([]byte, len(p)) 68 | copy(capturedData, p) 69 | return len(p), nil 70 | }) 71 | 72 | testData := []byte("test data") 73 | n, err := w.Write(testData) 74 | 75 | assert.NoError(t, err) 76 | assert.Equal(t, len(testData), n) 77 | assert.True(t, called) 78 | assert.Equal(t, testData, capturedData) 79 | } 80 | -------------------------------------------------------------------------------- /.github/workflows/advisor-test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Build and Test Advisor 4 | 5 | on: 6 | workflow_dispatch: {} 7 | push: 8 | branches: 9 | - main 10 | paths: 11 | - .github/workflows/build-go.yaml 12 | - 'advisor/*.go' 13 | pull_request: 14 | branches: 15 | - main 16 | paths: 17 | - .github/workflows/build-go.yaml 18 | - 'advisor/*.go' 19 | 20 | permissions: 21 | contents: read 22 | 23 | defaults: 24 | run: 25 | working-directory: ./advisor 26 | 27 | jobs: 28 | lint: 29 | runs-on: ubuntu-latest 30 | strategy: 31 | matrix: 32 | go-version: [ '1.24.x' ] 33 | steps: 34 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 35 | 36 | - name: Setup Go 37 | uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 38 | with: 39 | go-version: ${{ matrix.go-version }} 40 | cache: false 41 | 42 | - name: golangci-lint 43 | uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9 44 | with: 45 | version: latest 46 | working-directory: ./advisor 47 | args: --timeout=5m 48 | 49 | build: 50 | runs-on: ubuntu-latest 51 | needs: [lint] 52 | strategy: 53 | matrix: 54 | go-version: [ '1.24.x' ] 55 | steps: 56 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 57 | 58 | - name: Setup Go ${{ matrix.go-version }} 59 | uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6 60 | with: 61 | go-version: ${{ matrix.go-version }} 62 | 63 | - name: Install dependencies 64 | run: go get -v ./... 65 | 66 | - name: Build 67 | run: go build -v ./... 68 | 69 | - name: Test with the Go CLI 70 | run: go test -v ./... > test-results-${{ matrix.go-version }}.json 71 | 72 | - name: Upload Go test results 73 | uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 74 | with: 75 | name: test-results-${{ matrix.go-version }} 76 | path: test-results-${{ matrix.go-version }}.json 77 | -------------------------------------------------------------------------------- /advisor/cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | 7 | log "github.com/rs/zerolog/log" 8 | "github.com/spf13/cobra" 9 | "github.com/kguardian-dev/kguardian/advisor/pkg/k8s" 10 | ) 11 | 12 | // Version information - these will be set during build 13 | var ( 14 | Version = "development" 15 | BuildDate = "unknown" 16 | GitCommit = "unknown" 17 | ) 18 | 19 | var versionCmd = &cobra.Command{ 20 | Use: "version", 21 | Short: "Print the client and server version information", 22 | Long: `Display the client version and, if connected to a Kubernetes server, the server version as well.`, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | // Set up the logger first, so we get useful debug output 25 | setupLogger() 26 | 27 | // Display client version information 28 | fmt.Printf("Client Version:\n") 29 | fmt.Printf(" Version: %s\n", Version) 30 | fmt.Printf(" Git Commit: %s\n", GitCommit) 31 | fmt.Printf(" Build Date: %s\n", BuildDate) 32 | fmt.Printf(" Go Version: %s\n", runtime.Version()) 33 | fmt.Printf(" Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH) 34 | 35 | // Try to get server version information 36 | fmt.Printf("\nServer Version:\n") 37 | 38 | // Get Kubernetes config 39 | config, err := k8s.GetConfig(true) // Use dry-run mode 40 | if err != nil { 41 | log.Debug().Err(err).Msg("Failed to get Kubernetes configuration") 42 | fmt.Printf(" Unable to connect to Kubernetes server: %v\n", err) 43 | return 44 | } 45 | 46 | if config.Clientset == nil { 47 | log.Debug().Msg("Kubernetes clientset is nil") 48 | fmt.Printf(" Not connected to a Kubernetes server\n") 49 | return 50 | } 51 | 52 | // Get server version 53 | serverVersion, err := config.Clientset.Discovery().ServerVersion() 54 | if err != nil { 55 | log.Debug().Err(err).Msg("Failed to get server version") 56 | fmt.Printf(" Unable to retrieve server version: %v\n", err) 57 | return 58 | } 59 | 60 | fmt.Printf(" Version: %s\n", serverVersion.GitVersion) 61 | fmt.Printf(" Platform: %s/%s\n", serverVersion.Platform, serverVersion.GoVersion) 62 | fmt.Printf(" Build Date: %s\n", serverVersion.BuildDate) 63 | }, 64 | } 65 | 66 | func init() { 67 | rootCmd.AddCommand(versionCmd) 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/frontend-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Build and Push UI Docker Images 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | tags: 9 | - "frontend/v*" 10 | 11 | jobs: 12 | build_and_push: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 17 | 18 | - name: Cache Node modules 19 | uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 20 | with: 21 | path: | 22 | ~/.npm 23 | frontend/node_modules 24 | key: ${{ runner.os }}-node-${{ hashFiles('frontend/package-lock.json') }} 25 | restore-keys: | 26 | ${{ runner.os }}-node- 27 | 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 30 | with: 31 | platforms: all 32 | 33 | - name: Set up Docker Buildx 34 | id: buildx 35 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 36 | with: 37 | install: true 38 | version: latest 39 | 40 | - name: Login to GHCR 41 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 42 | with: 43 | registry: ghcr.io 44 | username: ${{ github.actor }} 45 | password: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Extract tag name 48 | id: extract_tag 49 | run: | 50 | TAG="${GITHUB_REF#refs/tags/}" 51 | VERSION="${TAG#frontend/}" 52 | echo "TAG_NAME=${VERSION}" >> $GITHUB_OUTPUT 53 | 54 | - name: Build and Push 55 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 56 | with: 57 | context: frontend/ 58 | file: frontend/Dockerfile 59 | platforms: linux/amd64,linux/arm64 60 | push: true 61 | tags: | 62 | ghcr.io/kguardian-dev/kguardian/frontend:${{ steps.extract_tag.outputs.TAG_NAME }} 63 | ghcr.io/kguardian-dev/kguardian/frontend:latest 64 | cache-from: type=gha 65 | cache-to: type=gha,mode=max 66 | -------------------------------------------------------------------------------- /frontend/src/services/aiApi.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // LLM Bridge URL - use relative path for proxy in production, or direct URL in development 4 | // In production (Vite preview), this proxies through /llm-api to the llm-bridge service 5 | // In development, this can connect directly to localhost:8080 or use the dev proxy 6 | const LLM_BRIDGE_URL = import.meta.env.PROD ? '/llm-api' : (import.meta.env.VITE_LLM_BRIDGE_URL || 'http://localhost:8080'); 7 | 8 | export type LLMProvider = 'openai' | 'anthropic' | 'gemini' | 'copilot'; 9 | 10 | export interface HistoryMessage { 11 | role: 'user' | 'assistant' | 'system'; 12 | content: string; 13 | } 14 | 15 | export interface ChatMessage { 16 | message: string; 17 | conversation_id?: string; 18 | provider?: LLMProvider; 19 | model?: string; 20 | system_prompt?: string; 21 | history?: HistoryMessage[]; 22 | } 23 | 24 | export interface ChatResponse { 25 | message: string; 26 | provider: LLMProvider; 27 | model: string; 28 | conversation_id?: string; 29 | } 30 | 31 | /** 32 | * Send a chat message to the AI assistant 33 | * @param message The user's message 34 | * @param history Optional: Previous conversation messages for context 35 | * @param provider Optional: Specify which LLM provider to use (openai, anthropic, gemini, copilot) 36 | * @param conversationId Optional: Continue an existing conversation 37 | * @returns The AI's response 38 | */ 39 | export async function sendChatMessage( 40 | message: string, 41 | history?: HistoryMessage[], 42 | provider?: LLMProvider, 43 | conversationId?: string 44 | ): Promise { 45 | try { 46 | const response = await axios.post(`${LLM_BRIDGE_URL}/api/chat`, { 47 | message, 48 | history, 49 | provider, 50 | conversationId, 51 | }); 52 | 53 | return response.data; 54 | } catch (error) { 55 | if (axios.isAxiosError(error)) { 56 | const errorMessage = error.response?.data?.error || error.message; 57 | const details = error.response?.data?.details; 58 | throw new Error( 59 | `Failed to get AI response: ${errorMessage}${details ? ` - ${details}` : ''}` 60 | ); 61 | } 62 | throw new Error('An unexpected error occurred while calling the AI API'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | 8 | // Development server configuration 9 | server: { 10 | allowedHosts: true, 11 | proxy: { 12 | '/api': { 13 | target: process.env.VITE_API_URL || 'http://localhost:9090', 14 | changeOrigin: true, 15 | rewrite: (path) => path.replace(/^\/api/, ''), 16 | }, 17 | '/llm-api': { 18 | target: process.env.VITE_LLM_BRIDGE_URL || 'http://localhost:8080', 19 | changeOrigin: true, 20 | rewrite: (path) => path.replace(/^\/llm-api/, ''), 21 | }, 22 | }, 23 | }, 24 | 25 | // Production build configuration 26 | build: { 27 | // Output directory for production build 28 | outDir: 'dist', 29 | 30 | // Generate sourcemaps for production debugging (optional, disable for smaller builds) 31 | sourcemap: false, 32 | 33 | // Target modern browsers for smaller bundles 34 | target: 'esnext', 35 | 36 | // Optimize chunk splitting 37 | rollupOptions: { 38 | output: { 39 | // Manual chunk splitting for better caching 40 | manualChunks: { 41 | // Vendor chunks 42 | 'react-vendor': ['react', 'react-dom'], 43 | 'react-flow-vendor': ['reactflow'], 44 | }, 45 | }, 46 | }, 47 | 48 | // Chunk size warning limit (500 KB) 49 | chunkSizeWarningLimit: 500, 50 | 51 | // Minification 52 | minify: 'esbuild', 53 | 54 | // Asset optimization 55 | assetsInlineLimit: 4096, // 4kb - inline assets smaller than this 56 | }, 57 | 58 | // Preview server configuration (for production) 59 | preview: { 60 | port: 5173, 61 | host: '0.0.0.0', 62 | strictPort: true, 63 | allowedHosts: true, 64 | proxy: { 65 | '/api': { 66 | target: process.env.VITE_API_URL || 'http://localhost:9090', 67 | changeOrigin: true, 68 | rewrite: (path) => path.replace(/^\/api/, ''), 69 | }, 70 | '/llm-api': { 71 | target: process.env.VITE_LLM_BRIDGE_URL || 'http://localhost:8080', 72 | changeOrigin: true, 73 | rewrite: (path) => path.replace(/^\/llm-api/, ''), 74 | }, 75 | }, 76 | }, 77 | }) 78 | -------------------------------------------------------------------------------- /mcp-server/tools/service_details.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // ServiceDetailsInput defines the input parameters for getting service details by IP 15 | type ServiceDetailsInput struct { 16 | IP string `json:"ip" jsonschema:"The IP address of the service to query"` 17 | } 18 | 19 | // ServiceDetailsOutput defines the output structure 20 | type ServiceDetailsOutput struct { 21 | Data string `json:"data" jsonschema:"Service details in JSON format"` 22 | } 23 | 24 | // ServiceDetailsHandler handles the get_service_details tool 25 | type ServiceDetailsHandler struct { 26 | client *BrokerClient 27 | } 28 | 29 | // Call implements the tool handler 30 | func (h ServiceDetailsHandler) Call( 31 | ctx context.Context, 32 | req *mcp.CallToolRequest, 33 | input ServiceDetailsInput, 34 | ) (*mcp.CallToolResult, ServiceDetailsOutput, error) { 35 | startTime := time.Now() 36 | logger.Log.WithField("ip", input.IP).Info("Received get_service_details request") 37 | 38 | if input.IP == "" { 39 | logger.Log.Error("IP address is required but not provided") 40 | return nil, ServiceDetailsOutput{}, fmt.Errorf("IP address is required") 41 | } 42 | 43 | data, err := h.client.GetServiceByIP(input.IP) 44 | if err != nil { 45 | logger.Log.WithFields(logrus.Fields{ 46 | "ip": input.IP, 47 | "error": err.Error(), 48 | "total_duration": time.Since(startTime).String(), 49 | }).Error("Error fetching service details") 50 | return nil, ServiceDetailsOutput{}, fmt.Errorf("error fetching service details: %w", err) 51 | } 52 | 53 | jsonData, err := json.MarshalIndent(data, "", " ") 54 | if err != nil { 55 | logger.Log.WithField("error", err.Error()).Error("Error marshaling response") 56 | return nil, ServiceDetailsOutput{}, fmt.Errorf("error marshaling response: %w", err) 57 | } 58 | 59 | logger.Log.WithFields(logrus.Fields{ 60 | "ip": input.IP, 61 | "response_bytes": len(jsonData), 62 | "total_duration": time.Since(startTime).String(), 63 | }).Info("Successfully fetched service details") 64 | 65 | return nil, ServiceDetailsOutput{Data: string(jsonData)}, nil 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/mcp-server-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Build and Push MCP Server Docker Images 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | tags: 9 | - "mcp-server/v*" 10 | 11 | jobs: 12 | build_and_push: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 17 | 18 | - name: Cache Go modules 19 | uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 20 | with: 21 | path: | 22 | ~/.cache/go-build 23 | ~/go/pkg/mod 24 | key: ${{ runner.os }}-go-mcp-server-${{ hashFiles('mcp-server/go.sum') }} 25 | restore-keys: | 26 | ${{ runner.os }}-go-mcp-server- 27 | ${{ runner.os }}-go- 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 31 | with: 32 | platforms: all 33 | 34 | - name: Set up Docker Buildx 35 | id: buildx 36 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 37 | with: 38 | install: true 39 | version: latest 40 | 41 | - name: Login to GHCR 42 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 43 | with: 44 | registry: ghcr.io 45 | username: ${{ github.actor }} 46 | password: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Extract tag name 49 | id: extract_tag 50 | run: | 51 | TAG="${GITHUB_REF#refs/tags/}" 52 | VERSION="${TAG#mcp-server/}" 53 | echo "TAG_NAME=${VERSION}" >> $GITHUB_OUTPUT 54 | 55 | - name: Build and Push 56 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 57 | with: 58 | context: mcp-server/ 59 | file: mcp-server/Dockerfile 60 | platforms: linux/amd64,linux/arm64 61 | push: true 62 | tags: | 63 | ghcr.io/kguardian-dev/kguardian/mcp-server:${{ steps.extract_tag.outputs.TAG_NAME }} 64 | ghcr.io/kguardian-dev/kguardian/mcp-server:latest 65 | cache-from: type=gha 66 | cache-to: type=gha,mode=max 67 | -------------------------------------------------------------------------------- /docs/essentials/navigation.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Navigation' 3 | description: 'The navigation field in docs.json defines the pages that go in the navigation menu' 4 | icon: 'map' 5 | --- 6 | 7 | The navigation menu is the list of links on every website. 8 | 9 | You will likely update `docs.json` every time you add a new page. Pages do not show up automatically. 10 | 11 | ## Navigation syntax 12 | 13 | Our navigation syntax is recursive which means you can make nested navigation groups. You don't need to include `.mdx` in page names. 14 | 15 | 16 | 17 | ```json Regular Navigation 18 | "navigation": { 19 | "tabs": [ 20 | { 21 | "tab": "Docs", 22 | "groups": [ 23 | { 24 | "group": "Getting Started", 25 | "pages": ["quickstart"] 26 | } 27 | ] 28 | } 29 | ] 30 | } 31 | ``` 32 | 33 | ```json Nested Navigation 34 | "navigation": { 35 | "tabs": [ 36 | { 37 | "tab": "Docs", 38 | "groups": [ 39 | { 40 | "group": "Getting Started", 41 | "pages": [ 42 | "quickstart", 43 | { 44 | "group": "Nested Reference Pages", 45 | "pages": ["nested-reference-page"] 46 | } 47 | ] 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | ``` 54 | 55 | 56 | 57 | ## Folders 58 | 59 | Simply put your MDX files in folders and update the paths in `docs.json`. 60 | 61 | For example, to have a page at `https://yoursite.com/your-folder/your-page` you would make a folder called `your-folder` containing an MDX file called `your-page.mdx`. 62 | 63 | 64 | 65 | You cannot use `api` for the name of a folder unless you nest it inside another folder. Mintlify uses Next.js which reserves the top-level `api` folder for internal server calls. A folder name such as `api-reference` would be accepted. 66 | 67 | 68 | 69 | ```json Navigation With Folder 70 | "navigation": { 71 | "tabs": [ 72 | { 73 | "tab": "Docs", 74 | "groups": [ 75 | { 76 | "group": "Group Name", 77 | "pages": ["your-folder/your-page"] 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | ``` 84 | 85 | ## Hidden pages 86 | 87 | MDX files not included in `docs.json` will not show up in the sidebar but are accessible through the search bar and by linking directly to them. 88 | -------------------------------------------------------------------------------- /.github/workflows/llm-bridge-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Build and Push LLM Bridge Docker Images 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | tags: 9 | - "llm-bridge/v*" 10 | 11 | jobs: 12 | build_and_push: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 17 | 18 | - name: Cache Node modules 19 | uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 20 | with: 21 | path: | 22 | ~/.npm 23 | llm-bridge/node_modules 24 | key: ${{ runner.os }}-node-llm-bridge-${{ hashFiles('llm-bridge/package-lock.json') }} 25 | restore-keys: | 26 | ${{ runner.os }}-node-llm-bridge- 27 | ${{ runner.os }}-node- 28 | 29 | - name: Set up QEMU 30 | uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 31 | with: 32 | platforms: all 33 | 34 | - name: Set up Docker Buildx 35 | id: buildx 36 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 37 | with: 38 | install: true 39 | version: latest 40 | 41 | - name: Login to GHCR 42 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 43 | with: 44 | registry: ghcr.io 45 | username: ${{ github.actor }} 46 | password: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | - name: Extract tag name 49 | id: extract_tag 50 | run: | 51 | TAG="${GITHUB_REF#refs/tags/}" 52 | VERSION="${TAG#llm-bridge/}" 53 | echo "TAG_NAME=${VERSION}" >> $GITHUB_OUTPUT 54 | 55 | - name: Build and Push 56 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 57 | with: 58 | context: llm-bridge/ 59 | file: llm-bridge/Dockerfile 60 | platforms: linux/amd64,linux/arm64 61 | push: true 62 | tags: | 63 | ghcr.io/kguardian-dev/kguardian/llm-bridge:${{ steps.extract_tag.outputs.TAG_NAME }} 64 | ghcr.io/kguardian-dev/kguardian/llm-bridge:latest 65 | cache-from: type=gha 66 | cache-to: type=gha,mode=max 67 | -------------------------------------------------------------------------------- /charts/kguardian/README.md.gotmpl: -------------------------------------------------------------------------------- 1 | # kguardian Helm Chart 2 | 3 | This chart bootstraps the [kguardian]() controlplane onto a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. 4 | 5 | {{ template "chart.versionBadge" . }} 6 | 7 | ## Overview 8 | 9 | This Helm chart deploys: 10 | 11 | - A kguardian control plane configured to your specifications 12 | - Additional features and components (optional) 13 | 14 | ## Prerequisites 15 | 16 | - Linux Kernel 6.2+ 17 | - Kubernetes 1.19+ 18 | - kubectl v1.19+ 19 | - Helm 3.0+ 20 | 21 | ## Install the Chart 22 | 23 | To install the chart with the release name `kguardian`: 24 | 25 | ### Install from OCI Registry (Recommended) 26 | 27 | ```bash 28 | helm install kguardian oci://ghcr.io/kguardian-dev/charts/kguardian \ 29 | --namespace kguardian \ 30 | --create-namespace 31 | ``` 32 | 33 | You can also specify a version: 34 | 35 | ```bash 36 | helm install kguardian oci://ghcr.io/kguardian-dev/charts/kguardian \ 37 | --version 1.1.1 \ 38 | --namespace kguardian \ 39 | --create-namespace 40 | ``` 41 | 42 | **Note:** *If you have the [Pod Securty Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) enabled for your cluster you will need to add the following annotation to the namespace that the chart is deployed* 43 | 44 | Example: 45 | 46 | ```yaml 47 | apiVersion: v1 48 | kind: Namespace 49 | metadata: 50 | labels: 51 | pod-security.kubernetes.io/enforce: privileged 52 | pod-security.kubernetes.io/warn: privileged 53 | name: kguardian 54 | ``` 55 | 56 | ## Directory Structure 57 | 58 | The following shows the directory structure of the Helm chart. 59 | 60 | ```bash 61 | charts/kguardian/ 62 | ├── .helmignore # Contains patterns to ignore when packaging Helm charts. 63 | ├── Chart.yaml # Information about your chart 64 | ├── values.yaml # The default values for your templates 65 | ├── charts/ # Charts that this chart depends on 66 | └── templates/ # The template files 67 | └── tests/ # The test files 68 | ``` 69 | 70 | ## Configuration 71 | 72 | The following table lists the configurable parameters of the kguardian chart and their default values. 73 | 74 | {{ template "chart.valuesTable" . }} 75 | 76 | ## Uninstalling the Chart 77 | 78 | To uninstall/delete the my-release deployment: 79 | 80 | ```bash 81 | helm uninstall my-release 82 | ``` 83 | -------------------------------------------------------------------------------- /advisor/pkg/network/types_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/kguardian-dev/kguardian/advisor/pkg/api" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | ) 10 | 11 | func TestGetPolicyName(t *testing.T) { 12 | assert.Equal(t, "test-pod-standard-policy", GetPolicyName("test-pod", "standard-policy")) 13 | assert.Equal(t, "another-pod-cilium-policy", GetPolicyName("another-pod", "cilium-policy")) 14 | } 15 | 16 | func TestCreateStandardLabels(t *testing.T) { 17 | expected := map[string]string{ 18 | "app.kubernetes.io/name": "my-pod", 19 | "app.kubernetes.io/component": "networkpolicy", 20 | "app.kubernetes.io/part-of": "kguardian", 21 | } 22 | assert.Equal(t, expected, CreateStandardLabels("my-pod", "networkpolicy")) 23 | } 24 | 25 | func TestCreateTypeMeta(t *testing.T) { 26 | expected := metav1.TypeMeta{ 27 | Kind: "NetworkPolicy", 28 | APIVersion: "networking.k8s.io/v1", 29 | } 30 | assert.Equal(t, expected, CreateTypeMeta("NetworkPolicy", "networking.k8s.io/v1")) 31 | } 32 | 33 | func TestCreateObjectMeta(t *testing.T) { 34 | labels := map[string]string{"app": "test"} 35 | expected := metav1.ObjectMeta{ 36 | Name: "test-name", 37 | Namespace: "test-ns", 38 | Labels: labels, 39 | } 40 | assert.Equal(t, expected, CreateObjectMeta("test-name", "test-ns", labels)) 41 | } 42 | 43 | func TestIsIngressTraffic(t *testing.T) { 44 | podDetail := &api.PodDetail{PodIP: "192.168.1.100"} 45 | 46 | ingressTraffic := api.PodTraffic{TrafficType: "INGRESS"} 47 | assert.True(t, IsIngressTraffic(ingressTraffic, podDetail)) 48 | 49 | egressTraffic := api.PodTraffic{TrafficType: "EGRESS"} 50 | assert.False(t, IsIngressTraffic(egressTraffic, podDetail)) 51 | 52 | otherTraffic := api.PodTraffic{TrafficType: "OTHER"} 53 | assert.False(t, IsIngressTraffic(otherTraffic, podDetail)) 54 | } 55 | 56 | func TestIsEgressTraffic(t *testing.T) { 57 | podDetail := &api.PodDetail{PodIP: "192.168.1.100"} 58 | 59 | ingressTraffic := api.PodTraffic{TrafficType: "INGRESS"} 60 | assert.False(t, IsEgressTraffic(ingressTraffic, podDetail)) 61 | 62 | egressTraffic := api.PodTraffic{TrafficType: "EGRESS"} 63 | assert.True(t, IsEgressTraffic(egressTraffic, podDetail)) 64 | 65 | otherTraffic := api.PodTraffic{TrafficType: "OTHER"} 66 | assert.False(t, IsEgressTraffic(otherTraffic, podDetail)) 67 | } 68 | -------------------------------------------------------------------------------- /mcp-server/tools/syscalls.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // SyscallsInput defines the input parameters for the syscalls tool 15 | type SyscallsInput struct { 16 | Namespace string `json:"namespace" jsonschema:"The Kubernetes namespace of the pod"` 17 | PodName string `json:"pod_name" jsonschema:"The name of the pod"` 18 | } 19 | 20 | // SyscallsOutput defines the output for the syscalls tool 21 | type SyscallsOutput struct { 22 | Data string `json:"data" jsonschema:"Syscall data in JSON format"` 23 | } 24 | 25 | // SyscallsHandler handles the get_pod_syscalls tool 26 | type SyscallsHandler struct { 27 | client *BrokerClient 28 | } 29 | 30 | // Call implements the tool handler 31 | func (h SyscallsHandler) Call( 32 | ctx context.Context, 33 | req *mcp.CallToolRequest, 34 | input SyscallsInput, 35 | ) (*mcp.CallToolResult, SyscallsOutput, error) { 36 | startTime := time.Now() 37 | logger.Log.WithFields(logrus.Fields{ 38 | "namespace": input.Namespace, 39 | "pod_name": input.PodName, 40 | }).Info("Received get_pod_syscalls request") 41 | 42 | // Fetch data from broker 43 | data, err := h.client.GetPodSyscalls(input.Namespace, input.PodName) 44 | if err != nil { 45 | logger.Log.WithFields(logrus.Fields{ 46 | "namespace": input.Namespace, 47 | "pod_name": input.PodName, 48 | "error": err.Error(), 49 | "total_duration": time.Since(startTime).String(), 50 | }).Error("Error fetching syscalls") 51 | return nil, SyscallsOutput{}, fmt.Errorf("error fetching syscalls: %w", err) 52 | } 53 | 54 | // Convert to JSON string 55 | jsonData, err := json.MarshalIndent(data, "", " ") 56 | if err != nil { 57 | logger.Log.WithField("error", err.Error()).Error("Error marshaling response") 58 | return nil, SyscallsOutput{}, fmt.Errorf("error marshaling response: %w", err) 59 | } 60 | 61 | logger.Log.WithFields(logrus.Fields{ 62 | "namespace": input.Namespace, 63 | "pod_name": input.PodName, 64 | "response_bytes": len(jsonData), 65 | "total_duration": time.Since(startTime).String(), 66 | }).Info("Successfully fetched syscalls") 67 | 68 | return nil, SyscallsOutput{ 69 | Data: string(jsonData), 70 | }, nil 71 | } 72 | -------------------------------------------------------------------------------- /controller/src/bpf/helper.h: -------------------------------------------------------------------------------- 1 | #include "vmlinux.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Use LRU_HASH for automatic eviction of stale entries 8 | struct 9 | { 10 | __uint(type, BPF_MAP_TYPE_LRU_HASH); 11 | __uint(max_entries, 10240); 12 | __type(key, u64); 13 | __type(value, u32); 14 | } inode_num SEC(".maps"); 15 | 16 | struct 17 | { 18 | __uint(type, BPF_MAP_TYPE_LRU_HASH); 19 | __uint(max_entries, 10240); 20 | __type(key, u32); 21 | __type(value, u32); 22 | } ignore_ips SEC(".maps"); 23 | 24 | struct 25 | { 26 | __uint(type, BPF_MAP_TYPE_HASH); 27 | __uint(max_entries, 512); 28 | __type(key, u32); 29 | __type(value, u32); 30 | } allowed_syscalls SEC(".maps"); 31 | 32 | // Common filtering helper to avoid code duplication 33 | // Optimized to check cheap conditions first before map lookups 34 | static __always_inline bool should_filter_traffic(__u32 saddr, __u32 daddr) 35 | { 36 | // Fast path: check cheap conditions first (no map lookups) 37 | 38 | // Filter same source and destination 39 | if (saddr == daddr) 40 | return true; 41 | 42 | // Filter localhost (127.0.0.1) - 0x7F000001 in network byte order is 0x0100007F 43 | __u32 localhost = 0x0100007F; 44 | if (saddr == localhost || daddr == localhost) 45 | return true; 46 | 47 | // Filter zero addresses 48 | if (saddr == 0 || daddr == 0) 49 | return true; 50 | 51 | // Slow path: map lookups only if cheap checks passed 52 | // Check ignore list (typically empty or small, so lookups are rare) 53 | if (bpf_map_lookup_elem(&ignore_ips, &saddr)) 54 | return true; 55 | 56 | if (bpf_map_lookup_elem(&ignore_ips, &daddr)) 57 | return true; 58 | 59 | return false; 60 | } 61 | 62 | // Helper to get user space inode and validate it exists 63 | static __always_inline bool get_and_validate_inum(struct sock *sk, __u64 *inum_out) 64 | { 65 | if (!sk) 66 | return false; 67 | 68 | __u32 net_ns_inum = 0; 69 | BPF_CORE_READ_INTO(&net_ns_inum, sk, __sk_common.skc_net.net, ns.inum); 70 | 71 | __u64 key = (__u64)net_ns_inum; 72 | __u32 *user_space_inum_ptr = bpf_map_lookup_elem(&inode_num, &key); 73 | 74 | if (!user_space_inum_ptr) 75 | return false; 76 | 77 | *inum_out = key; 78 | return true; 79 | } -------------------------------------------------------------------------------- /advisor/pkg/k8s/generic.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | 6 | log "github.com/rs/zerolog/log" 7 | corev1 "k8s.io/api/core/v1" 8 | ) 9 | 10 | // Version is set at build time using -ldflags 11 | var Version = "development" // default value 12 | 13 | // ModeType defines the mode of operation for generating network policies 14 | type ModeType int 15 | 16 | const ( 17 | SinglePod ModeType = iota 18 | AllPodsInNamespace 19 | AllPodsInAllNamespaces 20 | ) 21 | 22 | // GenerateOptions holds options for the GenerateNetworkPolicy function 23 | type GenerateOptions struct { 24 | Mode ModeType 25 | PodName string // Used if Mode is SinglePod 26 | Namespace string // Used if Mode is AllPodsInNamespace or SinglePod 27 | } 28 | 29 | // Exportable function variables for testing - REMOVED 30 | 31 | func GetResource(options GenerateOptions, config *Config) []corev1.Pod { 32 | var pods []corev1.Pod 33 | ctx := context.TODO() // Or pass a context if available 34 | 35 | switch options.Mode { 36 | case SinglePod: 37 | // Fetch the specified pod 38 | fetchedPod, err := GetPod(ctx, config, options.Namespace, options.PodName) 39 | if err != nil { 40 | // Log the error and return an empty slice instead of fatally exiting. 41 | log.Error().Err(err).Msgf("failed to get running pod %s in namespace %s", options.PodName, options.Namespace) 42 | return []corev1.Pod{} 43 | } 44 | pods = append(pods, *fetchedPod) 45 | 46 | case AllPodsInNamespace: 47 | // Fetch all running pods in the given namespace 48 | fetchedPods, err := GetPodsInNamespace(ctx, config, options.Namespace) 49 | if err != nil { 50 | log.Error().Err(err).Msgf("failed to fetch running pods in namespace %s", options.Namespace) 51 | // Return empty list on error, or handle differently as needed 52 | return []corev1.Pod{} 53 | } 54 | pods = append(pods, fetchedPods...) 55 | 56 | case AllPodsInAllNamespaces: 57 | // Fetch all running pods in all namespaces 58 | fetchedPods, err := GetAllPodsInAllNamespaces(ctx, config) 59 | if err != nil { 60 | log.Error().Err(err).Msgf("failed to fetch all running pods in all namespaces") 61 | // Return empty list on error, or handle differently as needed 62 | return []corev1.Pod{} 63 | } 64 | pods = append(pods, fetchedPods...) 65 | default: 66 | log.Error().Msgf("Unknown mode type: %v", options.Mode) 67 | return []corev1.Pod{} 68 | } 69 | return pods 70 | } 71 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | /* Internal theme variables */ 4 | :root, 5 | :root.dark { 6 | --theme-bg-dark: #0E1726; 7 | --theme-bg-darker: #0A0F1C; 8 | --theme-bg-card: #1A2332; 9 | --theme-border: #2A3647; 10 | --theme-text-primary: #F3F4F6; 11 | --theme-text-secondary: #9CA3AF; 12 | --theme-text-tertiary: #6B7280; 13 | } 14 | 15 | /* Light theme override */ 16 | :root.light { 17 | --theme-bg-dark: #F3F4F6; 18 | --theme-bg-darker: #FFFFFF; 19 | --theme-bg-card: #F9FAFB; 20 | --theme-border: #E5E7EB; 21 | --theme-text-primary: #111827; 22 | --theme-text-secondary: #4B5563; 23 | --theme-text-tertiary: #6B7280; 24 | } 25 | 26 | @theme { 27 | /* Hubble colors using theme-aware CSS variables */ 28 | --color-hubble-dark: var(--theme-bg-dark); 29 | --color-hubble-darker: var(--theme-bg-darker); 30 | --color-hubble-card: var(--theme-bg-card); 31 | --color-hubble-border: var(--theme-border); 32 | 33 | /* Theme-aware text colors (generates text-primary, text-secondary, text-tertiary) */ 34 | --color-primary: var(--theme-text-primary); 35 | --color-secondary: var(--theme-text-secondary); 36 | --color-tertiary: var(--theme-text-tertiary); 37 | 38 | /* Accent colors (same for both themes) */ 39 | --color-hubble-accent: #3B82F6; 40 | --color-hubble-success: #10B981; 41 | --color-hubble-warning: #F59E0B; 42 | --color-hubble-error: #EF4444; 43 | } 44 | 45 | @layer base { 46 | body { 47 | background-color: var(--theme-bg-darker); 48 | color: var(--theme-text-primary); 49 | margin: 0; 50 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; 51 | transition: background-color 0.3s ease, color 0.3s ease; 52 | } 53 | 54 | #root { 55 | width: 100%; 56 | height: 100vh; 57 | } 58 | } 59 | 60 | /* React Flow custom styles */ 61 | .react-flow { 62 | @apply bg-hubble-dark; 63 | } 64 | 65 | .react-flow__node { 66 | @apply rounded-lg shadow-lg; 67 | } 68 | 69 | .react-flow__edge-path { 70 | stroke-width: 2; 71 | } 72 | 73 | .react-flow__handle { 74 | @apply opacity-0; 75 | } 76 | 77 | .react-flow__controls { 78 | @apply bg-hubble-card border-hubble-border; 79 | } 80 | 81 | .react-flow__controls-button { 82 | @apply bg-hubble-card border-hubble-border text-secondary; 83 | } 84 | 85 | .react-flow__controls-button:hover { 86 | @apply bg-hubble-dark; 87 | } 88 | -------------------------------------------------------------------------------- /.github/workflows/broker-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: Build and Push Broker Docker Images 4 | 5 | on: 6 | workflow_dispatch: 7 | push: 8 | tags: 9 | - "broker/v*" 10 | 11 | jobs: 12 | build_and_push: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 17 | 18 | - name: Cache Rust dependencies 19 | uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5 20 | with: 21 | path: | 22 | ~/.cargo/bin/ 23 | ~/.cargo/registry/index/ 24 | ~/.cargo/registry/cache/ 25 | ~/.cargo/git/db/ 26 | broker/target/ 27 | key: ${{ runner.os }}-cargo-broker-${{ hashFiles('broker/Cargo.lock') }} 28 | restore-keys: | 29 | ${{ runner.os }}-cargo-broker- 30 | ${{ runner.os }}-cargo- 31 | 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 34 | with: 35 | platforms: all 36 | 37 | - name: Set up Docker Buildx 38 | id: buildx 39 | uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3 40 | with: 41 | install: true 42 | version: latest 43 | 44 | - name: Login to GHCR 45 | uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 46 | with: 47 | registry: ghcr.io 48 | username: ${{ github.actor }} 49 | password: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | - name: Extract tag name 52 | id: extract_tag 53 | run: | 54 | TAG="${GITHUB_REF#refs/tags/}" 55 | VERSION="${TAG#broker/}" 56 | echo "TAG_NAME=${VERSION}" >> $GITHUB_OUTPUT 57 | 58 | - name: Build and Push 59 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 60 | with: 61 | context: broker/ 62 | file: broker/Dockerfile 63 | platforms: linux/amd64 #,linux/arm64 64 | push: true 65 | tags: | 66 | ghcr.io/kguardian-dev/kguardian/broker:${{ steps.extract_tag.outputs.TAG_NAME }} 67 | ghcr.io/kguardian-dev/kguardian/broker:latest 68 | cache-from: type=gha 69 | cache-to: type=gha,mode=max 70 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json 3 | name: "Renovate" 4 | 5 | on: 6 | merge_group: 7 | workflow_dispatch: 8 | inputs: 9 | # https://docs.renovatebot.com/self-hosted-configuration/#dryrun 10 | dryRun: 11 | description: "Dry Run" 12 | default: "false" 13 | required: false 14 | # https://docs.renovatebot.com/examples/self-hosting/#about-the-log-level-numbers 15 | logLevel: 16 | description: "Log Level" 17 | default: "debug" 18 | required: false 19 | version: 20 | description: Renovate version 21 | default: latest 22 | required: false 23 | schedule: 24 | - cron: "0 * * * *" 25 | push: 26 | branches: 27 | - main 28 | paths: 29 | - .github/workflows/renovate.yaml 30 | - .github/renovate.json5 31 | - .github/renovate/**.json5 32 | 33 | concurrency: 34 | group: ${{ github.workflow }}-${{ github.event.number || github.ref }} 35 | cancel-in-progress: true 36 | 37 | env: 38 | LOG_LEVEL: "${{ inputs.logLevel || 'debug' }}" 39 | RENOVATE_AUTODISCOVER: true 40 | RENOVATE_AUTODISCOVER_FILTER: "${{ github.repository }}" 41 | RENOVATE_DRY_RUN: "${{ inputs.dryRun == true }}" 42 | RENOVATE_PLATFORM: github 43 | RENOVATE_PLATFORM_COMMIT: true 44 | WORKFLOW_RENOVATE_VERSION: "${{ inputs.version || 'latest' }}" 45 | 46 | jobs: 47 | renovate: 48 | name: Renovate 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 53 | 54 | - name: Validate Renovate Configuration 55 | uses: suzuki-shunsuke/github-action-renovate-config-validator@c22827f47f4f4a5364bdba19e1fe36907ef1318e # v1.1.1 56 | 57 | - name: Generate Token 58 | uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 59 | id: app-token 60 | with: 61 | app-id: "${{ secrets.ARX_GITHUB_APP_ID }}" 62 | private-key: "${{ secrets.ARX_GITHUB_APP_PRIVATE_KEY }}" 63 | 64 | - name: Renovate 65 | uses: renovatebot/github-action@502904f1cefdd70cba026cb1cbd8c53a1443e91b # v44.1.0 66 | with: 67 | configurationFile: .github/renovate.json5 68 | token: "x-access-token:${{ steps.app-token.outputs.token }}" 69 | renovate-version: "${{ env.WORKFLOW_RENOVATE_VERSION }}" 70 | -------------------------------------------------------------------------------- /charts/kguardian/templates/database/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.database.name }} 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app.kubernetes.io/name: {{ .Values.database.service.name }} 10 | template: 11 | metadata: 12 | {{- with .Values.database.podAnnotations }} 13 | annotations: 14 | {{- toYaml . | nindent 8 }} 15 | {{- end }} 16 | labels: 17 | {{- include "kguardian.labels" . | nindent 8 }} 18 | app.kubernetes.io/name: {{ .Values.database.service.name }} 19 | spec: 20 | serviceAccountName: {{ default "database" .Values.database.serviceAccount.name }} 21 | automountServiceAccountToken: true 22 | securityContext: 23 | {{- toYaml .Values.database.podSecurityContext | nindent 8 }} 24 | containers: 25 | - name: postgresdb 26 | {{- if .Values.database.image.sha }} 27 | image: "{{ .Values.database.image.repository }}@{{ .Values.database.image.sha }}" 28 | {{- else }} 29 | image: "{{ .Values.database.image.repository }}:{{ .Values.database.image.tag }}" 30 | {{- end }} 31 | imagePullPolicy: {{ .Values.database.image.pullPolicy }} 32 | ports: 33 | - containerPort: 5432 34 | securityContext: 35 | {{- toYaml .Values.database.securityContext | nindent 12 }} 36 | env: 37 | - name: POSTGRES_USER 38 | value: rust 39 | - name: POSTGRES_HOST_AUTH_METHOD 40 | value: trust 41 | - name: POSTGRES_DB 42 | value: kube 43 | volumeMounts: 44 | - mountPath: /var/lib/postgres/data 45 | name: db-data 46 | volumes: 47 | - name: db-data 48 | {{- if .Values.database.persistence.enabled }} 49 | persistentVolumeClaim: 50 | claimName: {{ .Values.database.persistence.existingClaim }} 51 | {{- else }} 52 | emptyDir: {} 53 | {{- end -}} 54 | {{- with .Values.database.nodeSelector }} 55 | nodeSelector: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.database.affinity }} 59 | affinity: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | {{- with .Values.database.tolerations }} 63 | tolerations: 64 | {{- toYaml . | nindent 8 }} 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /mcp-server/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 5 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 6 | github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q= 7 | github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= 8 | github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA= 9 | github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 13 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= 18 | github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= 19 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 20 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 21 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 22 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 23 | golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= 24 | golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= 25 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 26 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 28 | -------------------------------------------------------------------------------- /mcp-server/tools/cluster_traffic.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // ClusterTrafficInput defines the input parameters (no params needed) 15 | type ClusterTrafficInput struct{} 16 | 17 | // ClusterTrafficOutput defines the output structure 18 | type ClusterTrafficOutput struct { 19 | Data string `json:"data" jsonschema:"All pod traffic data in the cluster in JSON format"` 20 | } 21 | 22 | // ClusterTrafficHandler handles the get_cluster_traffic tool 23 | type ClusterTrafficHandler struct { 24 | client *BrokerClient 25 | } 26 | 27 | // Call implements the tool handler 28 | func (h ClusterTrafficHandler) Call( 29 | ctx context.Context, 30 | req *mcp.CallToolRequest, 31 | input ClusterTrafficInput, 32 | ) (*mcp.CallToolResult, ClusterTrafficOutput, error) { 33 | startTime := time.Now() 34 | logger.Log.Info("Received get_cluster_traffic request") 35 | 36 | fetchStart := time.Now() 37 | data, err := h.client.GetAllPodTraffic() 38 | fetchDuration := time.Since(fetchStart) 39 | 40 | if err != nil { 41 | logger.Log.WithFields(logrus.Fields{ 42 | "error": err.Error(), 43 | "fetch_duration": fetchDuration.String(), 44 | "total_duration": time.Since(startTime).String(), 45 | }).Error("Error fetching cluster traffic") 46 | return nil, ClusterTrafficOutput{}, fmt.Errorf("error fetching cluster traffic: %w", err) 47 | } 48 | 49 | marshalStart := time.Now() 50 | jsonData, err := json.MarshalIndent(data, "", " ") 51 | marshalDuration := time.Since(marshalStart) 52 | 53 | if err != nil { 54 | logger.Log.WithFields(logrus.Fields{ 55 | "error": err.Error(), 56 | "fetch_duration": fetchDuration.String(), 57 | "marshal_duration": marshalDuration.String(), 58 | "total_duration": time.Since(startTime).String(), 59 | }).Error("Error marshaling response") 60 | return nil, ClusterTrafficOutput{}, fmt.Errorf("error marshaling response: %w", err) 61 | } 62 | 63 | totalDuration := time.Since(startTime) 64 | logger.Log.WithFields(logrus.Fields{ 65 | "response_bytes": len(jsonData), 66 | "fetch_duration": fetchDuration.String(), 67 | "marshal_duration": marshalDuration.String(), 68 | "total_duration": totalDuration.String(), 69 | }).Info("Successfully fetched cluster traffic") 70 | 71 | return nil, ClusterTrafficOutput{Data: string(jsonData)}, nil 72 | } 73 | -------------------------------------------------------------------------------- /mcp-server/tools/network_traffic.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 10 | "github.com/modelcontextprotocol/go-sdk/mcp" 11 | "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // NetworkTrafficInput defines the input parameters for the network traffic tool 15 | type NetworkTrafficInput struct { 16 | Namespace string `json:"namespace" jsonschema:"The Kubernetes namespace of the pod"` 17 | PodName string `json:"pod_name" jsonschema:"The name of the pod"` 18 | } 19 | 20 | // NetworkTrafficOutput defines the output for the network traffic tool 21 | type NetworkTrafficOutput struct { 22 | Data string `json:"data" jsonschema:"Network traffic data in JSON format"` 23 | } 24 | 25 | // NetworkTrafficHandler handles the get_pod_network_traffic tool 26 | type NetworkTrafficHandler struct { 27 | client *BrokerClient 28 | } 29 | 30 | // Call implements the tool handler 31 | func (h NetworkTrafficHandler) Call( 32 | ctx context.Context, 33 | req *mcp.CallToolRequest, 34 | input NetworkTrafficInput, 35 | ) (*mcp.CallToolResult, NetworkTrafficOutput, error) { 36 | startTime := time.Now() 37 | logger.Log.WithFields(logrus.Fields{ 38 | "namespace": input.Namespace, 39 | "pod_name": input.PodName, 40 | }).Info("Received get_pod_network_traffic request") 41 | 42 | // Fetch data from broker 43 | data, err := h.client.GetPodNetworkTraffic(input.Namespace, input.PodName) 44 | if err != nil { 45 | logger.Log.WithFields(logrus.Fields{ 46 | "namespace": input.Namespace, 47 | "pod_name": input.PodName, 48 | "error": err.Error(), 49 | "total_duration": time.Since(startTime).String(), 50 | }).Error("Error fetching network traffic") 51 | return nil, NetworkTrafficOutput{}, fmt.Errorf("error fetching network traffic: %w", err) 52 | } 53 | 54 | // Convert to JSON string 55 | jsonData, err := json.MarshalIndent(data, "", " ") 56 | if err != nil { 57 | logger.Log.WithField("error", err.Error()).Error("Error marshaling response") 58 | return nil, NetworkTrafficOutput{}, fmt.Errorf("error marshaling response: %w", err) 59 | } 60 | 61 | logger.Log.WithFields(logrus.Fields{ 62 | "namespace": input.Namespace, 63 | "pod_name": input.PodName, 64 | "response_bytes": len(jsonData), 65 | "total_duration": time.Since(startTime).String(), 66 | }).Info("Successfully fetched network traffic") 67 | 68 | return nil, NetworkTrafficOutput{ 69 | Data: string(jsonData), 70 | }, nil 71 | } 72 | -------------------------------------------------------------------------------- /charts/kguardian/templates/mcp-server/mcpserver-crd.yaml: -------------------------------------------------------------------------------- 1 | {{- if and .Values.mcpServer.enabled .Values.mcpServer.useKmcp }} 2 | --- 3 | # MCPServer Custom Resource (managed by kmcp controller) 4 | # This replaces the standard Deployment when kmcp is enabled 5 | apiVersion: kagent.dev/v1alpha1 6 | kind: MCPServer 7 | metadata: 8 | name: {{ .Values.mcpServer.service.name }} 9 | namespace: {{ include "kguardian.namespace" . }} 10 | labels: 11 | {{- include "kguardian.labels" . | nindent 4 }} 12 | spec: 13 | # Deployment configuration - defines the container-based MCP server 14 | deployment: 15 | # Container image for the MCP server 16 | {{- if .Values.mcpServer.image.sha }} 17 | image: {{ .Values.mcpServer.image.repository }}@{{ .Values.mcpServer.image.sha }} 18 | {{- else }} 19 | image: {{ .Values.mcpServer.image.repository }}:{{ .Values.mcpServer.image.tag }} 20 | {{- end }} 21 | 22 | # Port the MCP server listens on 23 | port: {{ .Values.mcpServer.container.port }} 24 | 25 | # Environment variables (as key-value map) 26 | env: 27 | BROKER_URL: "http://{{ .Values.broker.service.name }}.{{ include "kguardian.namespace" . }}.svc.cluster.local:{{ .Values.broker.container.port }}" 28 | PORT: "{{ .Values.mcpServer.container.port }}" 29 | {{- range .Values.mcpServer.env }} 30 | {{ .name }}: {{ .value | quote }} 31 | {{- end }} 32 | 33 | # Optional: Secret references for sensitive data 34 | {{- with .Values.mcpServer.kmcp.secretRefs }} 35 | secretRefs: 36 | {{- toYaml . | nindent 6 }} 37 | {{- end }} 38 | 39 | # Transport type - how clients communicate with the MCP server 40 | # Valid values: stdio, http 41 | transportType: {{ .Values.mcpServer.kmcp.transport.type | default "http" }} 42 | 43 | # HTTP transport configuration 44 | {{- if or (eq (.Values.mcpServer.kmcp.transport.type | default "http") "http") (not .Values.mcpServer.kmcp.transport.type) }} 45 | httpTransport: 46 | # Target port for HTTP service 47 | targetPort: {{ .Values.mcpServer.container.port }} 48 | # Path where MCP server is accessible 49 | path: {{ .Values.mcpServer.kmcp.transport.path | default "/" }} 50 | {{- end }} 51 | 52 | # Optional: Authentication configuration 53 | {{- with .Values.mcpServer.kmcp.authn }} 54 | authn: 55 | {{- toYaml . | nindent 4 }} 56 | {{- end }} 57 | 58 | # Optional: Authorization rules (CEL-based) 59 | {{- with .Values.mcpServer.kmcp.authz }} 60 | authz: 61 | {{- toYaml . | nindent 4 }} 62 | {{- end }} 63 | {{- end }} 64 | -------------------------------------------------------------------------------- /charts/kguardian/templates/broker/deployment.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: {{ include "kguardian.name" . }}-broker 6 | labels: 7 | {{- include "kguardian.labels" . | nindent 4 }} 8 | spec: 9 | {{- if not .Values.broker.autoscaling.enabled }} 10 | replicas: {{ .Values.broker.replicaCount }} 11 | {{- end }} 12 | selector: 13 | matchLabels: 14 | app.kubernetes.io/name: {{ .Values.broker.service.name }} 15 | template: 16 | metadata: 17 | {{- with .Values.broker.podAnnotations }} 18 | annotations: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | labels: 22 | {{- include "kguardian.labels" . | nindent 8 }} 23 | app.kubernetes.io/name: {{ .Values.broker.service.name }} 24 | spec: 25 | automountServiceAccountToken: true 26 | {{- with .Values.broker.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- toYaml . | nindent 8 }} 29 | {{- end }} 30 | serviceAccountName: {{ default "broker" .Values.broker.serviceAccount.name }} 31 | securityContext: 32 | {{- toYaml .Values.broker.podSecurityContext | nindent 8 }} 33 | containers: 34 | - name: broker 35 | securityContext: 36 | {{- toYaml .Values.broker.securityContext | nindent 12 }} 37 | {{- if .Values.broker.image.sha }} 38 | image: "{{ .Values.broker.image.repository }}@{{ .Values.broker.image.sha }}" 39 | {{- else }} 40 | image: "{{ .Values.broker.image.repository }}:{{ .Values.broker.image.tag }}" 41 | {{- end }} 42 | imagePullPolicy: {{ .Values.broker.image.pullPolicy }} 43 | ports: 44 | - name: http 45 | containerPort: {{ .Values.broker.container.port }} 46 | protocol: TCP 47 | env: 48 | - name: DATABASE_URL 49 | value: "postgres://rust@{{ .Values.database.service.name }}.{{ include "kguardian.namespace" . }}.svc.cluster.local:{{ .Values.database.container.port }}/kube" 50 | {{- with .Values.broker.resources }} 51 | resources: 52 | {{- toYaml . | nindent 12 }} 53 | {{- end }} 54 | {{- with .Values.broker.nodeSelector }} 55 | nodeSelector: 56 | {{- toYaml . | nindent 8 }} 57 | {{- end }} 58 | {{- with .Values.broker.affinity }} 59 | affinity: 60 | {{- toYaml . | nindent 8 }} 61 | {{- end }} 62 | {{- with .Values.broker.tolerations }} 63 | tolerations: 64 | {{- toYaml . | nindent 8 }} 65 | {{- end }} 66 | -------------------------------------------------------------------------------- /scripts/quick-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the GitHub owner and repository 4 | GITHUB_OWNER="kguardian-dev" 5 | GITHUB_REPO="kguardian" 6 | RELEASE_BINARY_NAME="advisor" 7 | BINARY_NAME="kguardian" 8 | INSTALL_DIR="/usr/local/bin" 9 | TMP_DIR=$(mktemp -d) 10 | BINARY_PATH="$TMP_DIR/$BINARY_NAME" 11 | 12 | echo "Starting the installation of kubectl-$BINARY_NAME..." 13 | 14 | 15 | 16 | # Trap to ensure that the temporary directory gets cleaned up 17 | cleanup() { 18 | echo "Cleaning up temporary files..." 19 | rm -rf "$TMP_DIR" 20 | } 21 | trap cleanup EXIT 22 | 23 | # Detect OS and Arch 24 | echo "Detecting OS and architecture..." 25 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') 26 | ARCH=$(uname -m) 27 | if [ "$ARCH" = "x86_64" ]; then 28 | ARCH="amd64" 29 | elif [ "$ARCH" = "aarch64" ]; then 30 | ARCH="arm64" 31 | fi 32 | 33 | echo "Detected OS: $OS, Arch: $ARCH" 34 | 35 | # Get the latest advisor release tag 36 | echo "Fetching the latest advisor release tag..." 37 | LATEST_RELEASE_TAG=$(curl -s "https://api.github.com/repos/$GITHUB_OWNER/$GITHUB_REPO/releases?per_page=100" | \ 38 | grep '"tag_name"' | \ 39 | grep 'advisor/' | \ 40 | cut -d '"' -f 4 | \ 41 | sed 's/advisor\///' | \ 42 | sort -V -r | \ 43 | head -n 1 | \ 44 | sed 's/^/advisor\//') 45 | 46 | # Check if the latest release was found 47 | if [ -z "$LATEST_RELEASE_TAG" ]; then 48 | echo "Error: Failed to fetch the latest advisor release." 49 | exit 1 50 | fi 51 | 52 | echo "Latest advisor release tag: $LATEST_RELEASE_TAG" 53 | 54 | # Construct the download URL 55 | BINARY_URL="https://github.com/$GITHUB_OWNER/$GITHUB_REPO/releases/download/$LATEST_RELEASE_TAG/$RELEASE_BINARY_NAME-$OS-$ARCH" 56 | echo "Download URL: $BINARY_URL" 57 | 58 | # Download the release and set it as executable 59 | echo "Downloading the kubectl-$BINARY_NAME binary..." 60 | curl -sL "$BINARY_URL" -o "$BINARY_PATH" 61 | if [ $? -ne 0 ]; then 62 | echo "Error: Failed to download the binary." 63 | exit 1 64 | fi 65 | 66 | chmod +x "$BINARY_PATH" 67 | 68 | # Notify user about the need for elevated permissions 69 | echo "The kubectl-$BINARY_NAME binary needs to be moved to $INSTALL_DIR, which requires elevated permissions." 70 | echo "You may need to provide your password for sudo access." 71 | 72 | # Move the binary to /usr/local/bin and rename it 73 | sudo mv "$BINARY_PATH" "$INSTALL_DIR/kubectl-$BINARY_NAME" 74 | 75 | echo "Installation successful! 'kubectl-$BINARY_NAME' is now available in your PATH." 76 | echo "You can start using it with 'kubectl $BINARY_NAME'." 77 | 78 | # Cleanup is handled by the trap, but you can call it explicitly if desired 79 | cleanup 80 | -------------------------------------------------------------------------------- /broker/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | use actix_cors::Cors; 4 | use actix_web::{get, web, App, HttpResponse, HttpServer}; 5 | use api::{ 6 | add_pod_details, add_pods, add_pods_batch, add_pods_syscalls, add_svc_details, mark_pod_dead, 7 | establish_connection, get_pod_by_ip, get_pod_by_name, get_pod_details, get_pod_syscall_name, get_pod_traffic, 8 | get_pod_traffic_name, get_pods_by_node, get_svc_by_ip, 9 | }; 10 | 11 | use diesel::r2d2; 12 | use telemetry::init_logging; 13 | mod telemetry; 14 | 15 | use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; 16 | use tracing::info; 17 | pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./db/migrations"); 18 | 19 | type DB = diesel::pg::Pg; 20 | 21 | fn run_migrations( 22 | connection: &mut impl MigrationHarness, 23 | ) -> Result<(), Box> { 24 | connection.run_pending_migrations(MIGRATIONS)?; 25 | Ok(()) 26 | } 27 | 28 | #[actix_web::main] 29 | async fn main() -> Result<(), std::io::Error> { 30 | init_logging(); 31 | let manager = establish_connection(); 32 | let pool = r2d2::Pool::builder() 33 | .build(manager) 34 | .expect("Failed to create pool."); 35 | // RUN the migration schema 36 | let mut x = pool.get().unwrap(); 37 | let r = run_migrations(&mut x); 38 | if let Err(e) = r { 39 | panic!("DB Set up failed {}", e); 40 | } else { 41 | info!("DB setup success"); 42 | } 43 | HttpServer::new(move || { 44 | let cors = Cors::default() 45 | .allow_any_origin() 46 | .allow_any_method() 47 | .allow_any_header() 48 | .max_age(3600); 49 | 50 | App::new() 51 | .wrap(cors) 52 | .app_data(web::Data::new(pool.clone())) 53 | .service(add_pods) 54 | .service(add_pods_batch) 55 | .service(add_pod_details) 56 | .service(add_pods_syscalls) 57 | .service(get_pod_traffic) 58 | .service(get_pod_details) 59 | .service(add_svc_details) 60 | .service(get_pod_by_ip) 61 | .service(get_pod_by_name) 62 | .service(get_svc_by_ip) 63 | .service(get_pod_traffic_name) 64 | .service(get_pod_syscall_name) 65 | .service(get_pods_by_node) 66 | .service(mark_pod_dead) 67 | .service(health_check) 68 | }) 69 | .bind(("0.0.0.0", 9090))? 70 | .run() 71 | .await 72 | } 73 | 74 | #[get("/health")] 75 | pub async fn health_check() -> HttpResponse { 76 | HttpResponse::Ok() 77 | .content_type("application/json") 78 | .body("Healthy!") 79 | } 80 | -------------------------------------------------------------------------------- /frontend/src/utils/seccompProfileGenerator.ts: -------------------------------------------------------------------------------- 1 | import type { PodNodeData } from '../types'; 2 | import type { SeccompProfile, SeccompSyscall } from '../types/seccompProfile'; 3 | import { parseSyscallString } from './syscalls'; 4 | 5 | export function generateSeccompProfile(pod: PodNodeData): SeccompProfile { 6 | // Collect all unique syscalls from the pod's observed behavior 7 | const uniqueSyscalls = new Set(); 8 | 9 | pod.syscalls?.forEach((syscallRecord) => { 10 | if (syscallRecord.syscalls) { 11 | // Split comma-separated syscalls and validate 12 | const { valid } = parseSyscallString(syscallRecord.syscalls); 13 | 14 | // Add valid syscalls to set 15 | valid.forEach(syscall => uniqueSyscalls.add(syscall)); 16 | } 17 | }); 18 | 19 | // Create syscall rules - group all observed syscalls into one allow rule 20 | const syscallRules: SeccompSyscall[] = []; 21 | 22 | if (uniqueSyscalls.size > 0) { 23 | syscallRules.push({ 24 | names: Array.from(uniqueSyscalls).sort(), 25 | action: 'SCMP_ACT_ALLOW', 26 | }); 27 | } 28 | 29 | // Create the seccomp profile 30 | const profile: SeccompProfile = { 31 | defaultAction: 'SCMP_ACT_ERRNO', // Default to deny all syscalls not explicitly allowed 32 | architectures: [ 33 | 'SCMP_ARCH_X86_64', 34 | 'SCMP_ARCH_X86', 35 | 'SCMP_ARCH_X32', 36 | ], 37 | syscalls: syscallRules, 38 | }; 39 | 40 | return profile; 41 | } 42 | 43 | export function profileToJSON(profile: SeccompProfile): string { 44 | return JSON.stringify(profile, null, 2); 45 | } 46 | 47 | export function profileToYAML(profile: SeccompProfile, resourceName: string, namespace: string): string { 48 | const yaml: string[] = []; 49 | 50 | // Create a Kubernetes SeccompProfile CRD format 51 | yaml.push('apiVersion: security.kubernetes.io/v1alpha1'); 52 | yaml.push('kind: SeccompProfile'); 53 | yaml.push('metadata:'); 54 | yaml.push(` name: ${resourceName}-seccomp`); 55 | yaml.push(` namespace: ${namespace}`); 56 | yaml.push('spec:'); 57 | yaml.push(` defaultAction: ${profile.defaultAction}`); 58 | 59 | if (profile.architectures && profile.architectures.length > 0) { 60 | yaml.push(' architectures:'); 61 | profile.architectures.forEach(arch => { 62 | yaml.push(` - ${arch}`); 63 | }); 64 | } 65 | 66 | if (profile.syscalls && profile.syscalls.length > 0) { 67 | yaml.push(' syscalls:'); 68 | profile.syscalls.forEach((syscall) => { 69 | yaml.push(' - names:'); 70 | syscall.names.forEach(name => { 71 | yaml.push(` - ${name}`); 72 | }); 73 | yaml.push(` action: ${syscall.action}`); 74 | }); 75 | } 76 | 77 | return yaml.join('\n'); 78 | } 79 | -------------------------------------------------------------------------------- /mcp-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | 11 | "github.com/kguardian-dev/kguardian/mcp-server/logger" 12 | "github.com/kguardian-dev/kguardian/mcp-server/tools" 13 | "github.com/modelcontextprotocol/go-sdk/mcp" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | func main() { 18 | // Initialize logger 19 | logLevel := os.Getenv("LOG_LEVEL") 20 | if logLevel == "" { 21 | logLevel = "info" 22 | } 23 | logger.Init(logLevel) 24 | 25 | // Get configuration from environment 26 | brokerURL := os.Getenv("BROKER_URL") 27 | if brokerURL == "" { 28 | brokerURL = "http://broker.kguardian.svc.cluster.local:9090" 29 | } 30 | 31 | port := os.Getenv("PORT") 32 | if port == "" { 33 | port = "8081" 34 | } 35 | 36 | logger.Log.WithFields(logrus.Fields{ 37 | "port": port, 38 | "broker_url": brokerURL, 39 | "log_level": logLevel, 40 | }).Info("Initializing kguardian MCP server") 41 | 42 | // Create MCP server 43 | server := mcp.NewServer( 44 | &mcp.Implementation{ 45 | Name: "kguardian-mcp", 46 | Version: "1.0.0", 47 | }, 48 | nil, 49 | ) 50 | 51 | // Register tools 52 | tools.RegisterTools(server, brokerURL) 53 | 54 | // Create HTTP handler using StreamableHTTPHandler 55 | handler := mcp.NewStreamableHTTPHandler(func(req *http.Request) *mcp.Server { 56 | return server 57 | }, nil) 58 | 59 | // Setup HTTP server 60 | httpServer := &http.Server{ 61 | Addr: ":" + port, 62 | Handler: handler, 63 | ReadTimeout: 30 * time.Second, 64 | WriteTimeout: 120 * time.Second, // Allow enough time for broker queries and large responses 65 | IdleTimeout: 120 * time.Second, 66 | } 67 | 68 | // Start server in a goroutine 69 | go func() { 70 | logger.Log.WithFields(logrus.Fields{ 71 | "port": port, 72 | "address": ":" + port, 73 | }).Info("kguardian MCP server starting") 74 | 75 | if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { 76 | logger.Log.WithField("error", err.Error()).Error("Failed to start HTTP server") 77 | os.Exit(1) 78 | } 79 | }() 80 | 81 | // Wait for interrupt signal 82 | quit := make(chan os.Signal, 1) 83 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 84 | sig := <-quit 85 | 86 | logger.Log.WithField("signal", sig.String()).Info("Received shutdown signal") 87 | 88 | // Graceful shutdown 89 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 90 | defer cancel() 91 | 92 | if err := httpServer.Shutdown(ctx); err != nil { 93 | logger.Log.WithField("error", err.Error()).Error("Server forced to shutdown") 94 | } 95 | 96 | logger.Log.Info("Server stopped gracefully") 97 | } 98 | -------------------------------------------------------------------------------- /advisor/pkg/k8s/labels.go: -------------------------------------------------------------------------------- 1 | package k8s 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | api "github.com/kguardian-dev/kguardian/advisor/pkg/api" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | // DetectLabels detects the labels of a pod. 14 | func detectSelectorLabels(clientset *kubernetes.Clientset, origin interface{}) (map[string]string, error) { 15 | // Use type assertion to check the specific type 16 | switch o := origin.(type) { 17 | case *v1.Pod: 18 | return GetOwnerRef(clientset, o) 19 | case *api.PodDetail: 20 | return GetOwnerRef(clientset, &o.Pod) 21 | case *api.SvcDetail: 22 | var svc v1.Service 23 | svc = o.Service 24 | return svc.Spec.Selector, nil 25 | default: 26 | return nil, fmt.Errorf("detectSelectorLabels: unknown type") 27 | } 28 | } 29 | 30 | func GetOwnerRef(clientset *kubernetes.Clientset, pod *v1.Pod) (map[string]string, error) { 31 | ctx := context.TODO() 32 | 33 | // Check if the Pod has an owner 34 | if len(pod.OwnerReferences) > 0 { 35 | owner := pod.OwnerReferences[0] 36 | 37 | // TODO: If the resource no longer exists but the database has the log/entry this will cause it to break for this netpol 38 | 39 | // Based on the owner, get the controller object to check its labels 40 | switch owner.Kind { 41 | case "ReplicaSet": 42 | replicaSet, err := clientset.AppsV1().ReplicaSets(pod.Namespace).Get(ctx, owner.Name, metav1.GetOptions{}) 43 | if err != nil { 44 | return nil, err 45 | } 46 | deployment, err := clientset.AppsV1().Deployments(pod.Namespace).Get(ctx, replicaSet.OwnerReferences[0].Name, metav1.GetOptions{}) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return deployment.Spec.Selector.MatchLabels, nil 51 | 52 | case "StatefulSet": 53 | statefulSet, err := clientset.AppsV1().StatefulSets(pod.Namespace).Get(ctx, owner.Name, metav1.GetOptions{}) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return statefulSet.Spec.Selector.MatchLabels, nil 58 | 59 | case "DaemonSet": 60 | daemonSet, err := clientset.AppsV1().DaemonSets(pod.Namespace).Get(ctx, owner.Name, metav1.GetOptions{}) 61 | if err != nil { 62 | return nil, err 63 | } 64 | return daemonSet.Spec.Selector.MatchLabels, nil 65 | 66 | case "Job": 67 | job, err := clientset.BatchV1().Jobs(pod.Namespace).Get(ctx, owner.Name, metav1.GetOptions{}) 68 | if err != nil { 69 | return nil, err 70 | } 71 | return job.Spec.Selector.MatchLabels, nil 72 | 73 | // Add more controller kinds here if needed 74 | 75 | default: 76 | return nil, fmt.Errorf("unknown or unsupported ownerReference: %s", owner.String()) 77 | } 78 | } 79 | return pod.Labels, nil 80 | } 81 | -------------------------------------------------------------------------------- /docs/ai-tools/claude-code.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Claude Code setup" 3 | description: "Configure Claude Code for your documentation workflow" 4 | icon: "asterisk" 5 | --- 6 | 7 | Claude Code is Anthropic's official CLI tool. This guide will help you set up Claude Code to help you write and maintain your documentation. 8 | 9 | ## Prerequisites 10 | 11 | - Active Claude subscription (Pro, Max, or API access) 12 | 13 | ## Setup 14 | 15 | 1. Install Claude Code globally: 16 | 17 | ```bash 18 | npm install -g @anthropic-ai/claude-code 19 | ``` 20 | 21 | 2. Navigate to your docs directory. 22 | 3. (Optional) Add the `CLAUDE.md` file below to your project. 23 | 4. Run `claude` to start. 24 | 25 | ## Create `CLAUDE.md` 26 | 27 | Create a `CLAUDE.md` file at the root of your documentation repository to train Claude Code on your specific documentation standards: 28 | 29 | ````markdown 30 | # Mintlify documentation 31 | 32 | ## Working relationship 33 | - You can push back on ideas-this can lead to better documentation. Cite sources and explain your reasoning when you do so 34 | - ALWAYS ask for clarification rather than making assumptions 35 | - NEVER lie, guess, or make up information 36 | 37 | ## Project context 38 | - Format: MDX files with YAML frontmatter 39 | - Config: docs.json for navigation, theme, settings 40 | - Components: Mintlify components 41 | 42 | ## Content strategy 43 | - Document just enough for user success - not too much, not too little 44 | - Prioritize accuracy and usability of information 45 | - Make content evergreen when possible 46 | - Search for existing information before adding new content. Avoid duplication unless it is done for a strategic reason 47 | - Check existing patterns for consistency 48 | - Start by making the smallest reasonable changes 49 | 50 | ## Frontmatter requirements for pages 51 | - title: Clear, descriptive page title 52 | - description: Concise summary for SEO/navigation 53 | 54 | ## Writing standards 55 | - Second-person voice ("you") 56 | - Prerequisites at start of procedural content 57 | - Test all code examples before publishing 58 | - Match style and formatting of existing pages 59 | - Include both basic and advanced use cases 60 | - Language tags on all code blocks 61 | - Alt text on all images 62 | - Relative paths for internal links 63 | 64 | ## Git workflow 65 | - NEVER use --no-verify when committing 66 | - Ask how to handle uncommitted changes before starting 67 | - Create a new branch when no clear branch exists for changes 68 | - Commit frequently throughout development 69 | - NEVER skip or disable pre-commit hooks 70 | 71 | ## Do not 72 | - Skip frontmatter on any MDX file 73 | - Use absolute URLs for internal links 74 | - Include untested code examples 75 | - Make assumptions - always ask for clarification 76 | ```` 77 | -------------------------------------------------------------------------------- /frontend/src/hooks/policyEditor/usePolicyExport.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import type { NetworkPolicy } from '../../types/networkPolicy'; 3 | import type { SeccompProfile } from '../../types/seccompProfile'; 4 | import { policyToYAML } from '../../utils/networkPolicyGenerator'; 5 | import { profileToYAML, profileToJSON } from '../../utils/seccompProfileGenerator'; 6 | 7 | export type PolicyType = 'network' | 'seccomp'; 8 | 9 | interface UsePolicyExportProps { 10 | policyType: PolicyType; 11 | policy: NetworkPolicy | null; 12 | seccompProfile: SeccompProfile | null; 13 | podName: string; 14 | podIdentity?: string; 15 | podNamespace: string; 16 | yamlView: boolean; 17 | } 18 | 19 | export const usePolicyExport = ({ 20 | policyType, 21 | policy, 22 | seccompProfile, 23 | podName, 24 | podIdentity, 25 | podNamespace, 26 | yamlView, 27 | }: UsePolicyExportProps) => { 28 | const [copiedToClipboard, setCopiedToClipboard] = useState(false); 29 | 30 | const getExportContent = (): string | null => { 31 | if (policyType === 'network' && policy) { 32 | return policyToYAML(policy); 33 | } else if (policyType === 'seccomp' && seccompProfile) { 34 | // Use pod identity for resource name, fallback to pod name 35 | const resourceName = podIdentity || podName; 36 | return yamlView 37 | ? profileToYAML(seccompProfile, resourceName, podNamespace) 38 | : profileToJSON(seccompProfile); 39 | } 40 | return null; 41 | }; 42 | 43 | const handleCopy = () => { 44 | const content = getExportContent(); 45 | if (!content) return; 46 | 47 | navigator.clipboard.writeText(content); 48 | setCopiedToClipboard(true); 49 | setTimeout(() => setCopiedToClipboard(false), 2000); 50 | }; 51 | 52 | const handleDownload = () => { 53 | const content = getExportContent(); 54 | if (!content) return; 55 | 56 | let filename: string; 57 | let mimeType: string; 58 | 59 | if (policyType === 'network' && policy) { 60 | filename = `${policy.metadata.name}.yaml`; 61 | mimeType = 'text/yaml'; 62 | } else if (policyType === 'seccomp') { 63 | if (yamlView) { 64 | filename = `${podName}-seccomp.yaml`; 65 | mimeType = 'text/yaml'; 66 | } else { 67 | filename = `${podName}-seccomp.json`; 68 | mimeType = 'application/json'; 69 | } 70 | } else { 71 | return; 72 | } 73 | 74 | const blob = new Blob([content], { type: mimeType }); 75 | const url = URL.createObjectURL(blob); 76 | const a = document.createElement('a'); 77 | a.href = url; 78 | a.download = filename; 79 | a.click(); 80 | URL.revokeObjectURL(url); 81 | }; 82 | 83 | return { 84 | copiedToClipboard, 85 | handleCopy, 86 | handleDownload, 87 | }; 88 | }; 89 | --------------------------------------------------------------------------------