Compare commits

..

180 Commits

Author SHA1 Message Date
Alon Girmonsky
f97866f747 🔖 Release v53.3.0 (#1937)
* 🔖 Bump the Helm chart version to 53.3.0

* 🙈 Add .claude/ to .gitignore

* 🔥 Remove .claude/ and RELEASE_NOTES_v53.2.5.md

*  Revert changes to release-tag.yml

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-MacBook-Air.local>
Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-19 02:00:17 -07:00
Ilya Gavrilov
b2a0fb0cea Add L7 data boundaries MCP tool, API endpoint and frontend LIVE filter button (#1935)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-05-18 22:53:01 -07:00
Alon Girmonsky
2475f6e260 Add PostgreSQL protocol support to KFL skill (#1936)
Add PostgreSQL filter examples, variable reference table, and protocol
table entry. Notes the key difference that postgresql_error_code is a
string (SQLSTATE) unlike MySQL's int error code.

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-18 02:25:04 -07:00
Alon Girmonsky
cd13d8f89e Add security-audit skill for MITRE ATT&CK-based threat detection (#1934)
New skill that guides systematic 8-phase network security audits across
MITRE ATT&CK tactics using snapshot-based traffic analysis. Includes
threat catalog, KFL security filter reference, and report template.

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-15 10:16:37 -07:00
Alon Girmonsky
ad9dfbf5f9 Add install skill for Kubeshark deployment guidance (#1933)
* Add install skill for Kubeshark deployment guidance

New skill that helps users install and configure Kubeshark with a clear
CLI vs Helm decision tree, opinionated production defaults, and
platform-specific storage class recommendations.

* Add user-invocable flag to install skill frontmatter

* Add backup/overwrite check guidance for ~/.kubeshark/ config files

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-15 08:31:33 -07:00
Alon Girmonsky
ed1d2e1a4d Enable tlsx dissector by default (#1928)
Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-14 11:40:02 -07:00
Volodymyr Stoiko
7b5954ea00 helm: grant hub tokenreviews and label worker pods for internal auth (#1926)
* helm: grant hub tokenreviews and pass trusted controllers

Adds RBAC for hub to call the authentication.k8s.io/v1 TokenReview
endpoint, used by the new internalauth middleware to validate projected
ServiceAccountTokens presented by in-cluster gRPC callers.

Adds tap.internalAuth.trustedControllers value (empty by default),
threaded through to hub's -trusted-controllers flag as a CSV. Listing
a controller here lets pods owned by it authenticate to hub via the
projected SA token (audience kubeshark-hub). Hub-spawned Jobs are
always trusted regardless of this list. Hub matches OwnerReferences
by name AND UID, so a name-only forgery does not grant trust.

Sub-issue of kubeshark/hub#656.

* helm: inline trusted controllers in hub deployment template

The chart already knows its own controller names (worker DaemonSet
metadata.name is the literal "kubeshark-worker-daemon-set" in
09-worker-daemon-set.yaml). Pasting the same literal into a user-facing
tap.internalAuth.trustedControllers value adds a step without buying
anything — if the worker DS rename, the deployment template would have
to change in lockstep regardless.

Drop the values knob, render the flag unconditionally with the literal
worker DS name (matching the convention used elsewhere in this chart,
e.g. the hub deployment's {{ include "kubeshark.name" . }}-hub).

* helm: drop redundant comment on tokenreviews RBAC

* helm: drop -trusted-controllers flag (no caller today)

The flag was wiring forward-prep for a hypothetical worker->hub gRPC
caller from the DaemonSet. Hub-spawned Jobs (dissection-job) are
admitted via internalauth.RegisterSpawnedJob, not via this flag.
Re-add when an actual DaemonSet-deployed caller materializes.

* helm: label worker DS pods for hub internal auth

Worker pods don't call hub gRPC today, but pre-labeling the DS pod
template means a future worker->hub gRPC caller is one PR (worker-side)
away from working — no chart change required. Matches the generic
label-driven trust model in hub#783.

* helm: rename trust label to kubeshark.io/internal-auth

Matches the hub rename. Generic name so the same label can mark pods
trusted by future kubeshark services beyond hub.
2026-05-13 10:53:20 -07:00
Volodymyr Stoiko
8186b7891b Authz refactoring (helm chart + CLI) (#1921)
* Migrate auth.saml.roles to unified auth.roles

Follows the hub-side introduction of the backend-neutral AUTH_ROLES /
AUTH_ROLES_CLAIM / AUTH_DEFAULT_ROLE config (hub commit 51177bcb).
CLI and Helm chart now surface the unified location:

  tap.auth.roles         — map of role -> permissions (shared SAML/OIDC)
  tap.auth.rolesClaim    — token/assertion claim name carrying roles
  tap.auth.defaultRole   — fallback role for authenticated users with
                           no matching role in their token

Helm ConfigMap template emits AUTH_ROLES / AUTH_ROLES_CLAIM /
AUTH_DEFAULT_ROLE and no longer emits AUTH_SAML_ROLES or
AUTH_SAML_ROLE_ATTRIBUTE. Hub's back-compat fallback still reads those
keys from any existing ConfigMap that hasn't been helm-upgraded.

Legacy struct fields (SamlConfig.Roles, SamlConfig.RoleAttribute) stay
in place so existing values.yaml files with auth.saml.roles still parse
without errors, but the CLI and the chart ignore them. Follow-up release
can remove the struct fields once telemetry confirms migration.

Breaking for users with customized auth.saml.roles in their values.yaml
— the customization is masked by the new default auth.roles.admin and
must be migrated to auth.roles for the custom permissions to take
effect. Documented in the chart README and release notes.

Part of authz-refactoring (Step 2 of hub-oidc-rbac.md, CLI side).

* Remove legacy

* Align CLI + Helm chart with hub AUTH_TYPE rename

Follows hub commit 11564fef. The canonical AUTH_TYPE is now `oidc` for
generic OIDC; `dex` is a permanent alias; `descope` is a new explicit
label. This change surfaces the new vocabulary in the CLI config struct
and the Helm chart, and renames the nested `auth.dexOidc` values.yaml
field to `auth.oidc` for consistency.

Helm chart:
- 12-config-map.yaml: AUTH_OIDC_* keys now read `.Values.tap.auth.oidc.*`
  instead of `auth.dexOidc.*`. The cloud-license override that forced
  AUTH_TYPE=default unless the admin picked `dex` now accepts `oidc` too.
- 13-secret.yaml: OIDC_CLIENT_ID / OIDC_CLIENT_SECRET read from
  `auth.oidc.*` (was `auth.dexOidc.*`).
- 06-front-deployment.yaml: REACT_APP_AUTH_ENABLED / REACT_APP_AUTH_TYPE
  conditionals accept both `oidc` and `dex` where they previously only
  matched `dex`.
- values.yaml: comment on `tap.auth.type` lists valid values and flags
  the breaking change.
- README.md: `tap.auth.type` row lists valid values. All `dexOidc`
  references renamed to `oidc`. Sample values.yaml blocks now show
  `type: oidc` as the canonical form.

CLI:
- config/configStructs/tapConfig.go: AuthConfig.Type documented with the
  full list of valid values and the migration hint.

Breaking changes (repeated in release notes):
1. `tap.auth.type: oidc` now routes to the generic OIDC middleware
   (previously Descope). Switch to `tap.auth.type: descope` or `default`
   if you were using `oidc` for Descope.
2. `tap.auth.dexOidc.*` values are no longer read. Rename to
   `tap.auth.oidc.*`. No fallback.
3. `tap.auth.type: dex` continues to work — permanent alias of `oidc`.

Part of authz-refactoring (Step 4 of hub-oidc-rbac.md, CLI/Helm side).

* default kfl

* Authz Refactoring: Step 8: namespaces-list role filter

Align with hub PR kubeshark/hub#756. Per-role auth.roles[].filter (KFL)
is replaced by auth.roles[].namespaces (comma-separated list with "*",
literal, and glob semantics). Standalone tap.auth.defaultFilter knob
removed.

helm-chart/values.yaml
- admin role example uses namespaces: "*" instead of filter: "".
- Comment block explains the new namespaces semantics.
- defaultFilter: "" entry + accompanying comment block deleted.

helm-chart/templates/12-config-map.yaml
- AUTH_DEFAULT_FILTER ConfigMap entry removed (hub no longer reads it).

helm-chart/README.md
- tap.auth.defaultFilter row removed.
- tap.auth.roles default value example updated: filter: "" → namespaces: "*";
  description gains the per-role namespaces semantics legend.
2026-05-06 09:08:21 -07:00
Alon Girmonsky
ab81b0c3a7 🔖 Bump the Helm chart version to 53.2.5 (#1920)
Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-01 13:36:38 -07:00
Alon Girmonsky
9f5a1a41c0 fix(release-pr): sync bumped Chart.yaml to kubeshark.github.io (#1913)
* fix(release-pr): sync bumped Chart.yaml to kubeshark.github.io

The release-pr target was switching back to master (and pulling)
BEFORE copying helm-chart/ into ../kubeshark.github.io/charts/chart.
That reverted the working tree to the pre-bump Chart.yaml, so the
kubeshark.github.io PR shipped the previous version and the
chart-releaser action failed trying to recreate an existing tag.

Copy the bumped chart from the release/vX.Y.Z working tree, then
switch kubeshark back to master at the end of the target.

Also consolidate iterative robustness improvements: VERSION
validation, idempotent sibling-repo tagging, idempotent branch /
commit / push / PR creation, and a "nothing to commit" guard so
reruns of release-pr do not fail.

* refactor(release): split release-pr into three rerunnable targets

Before, release-pr did three things in one recipe: tag sibling
repos, create the kubeshark release PR, and create the helm chart
PR. If any step failed, the whole target had to be rerun, even for
the parts that had already succeeded, and some sub-steps (like
tagging worker/hub/front after a docker-image-only rebuild) had no
standalone entry point.

Split into:
  - release-siblings     : tag worker, hub, front
  - release-pr-kubeshark : bump Chart.yaml, build, open kubeshark PR
  - release-pr-helm      : sync chart to kubeshark.github.io, open helm PR
  - release-pr           : orchestrates all three in order

Each is idempotent and can be rerun independently. release-siblings
is now the canonical entry point for tagging sibling repos when
refreshing docker images without a full release.

release-pr-helm checks out release/v$(VERSION) (fetching from origin
if absent) before copying helm-chart/, so it has the bumped Chart.yaml
regardless of whether it runs right after release-pr-kubeshark or
days later in a separate invocation.

A shared _release-check-version prerequisite validates VERSION once
per target invocation.

* fix(release): make branch creation and push truly idempotent

Delete and recreate local release/helm branches instead of conditionally
checking out, and use --force-with-lease push to handle local/remote
divergence on reruns.

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-05-01 10:07:20 -07:00
Alon Girmonsky
fef3e8fb05 Add PostgreSQL protocol configuration (#1919)
* Add MySQL protocol to default configuration

Closes #1915

* Add PostgreSQL protocol configuration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 12:59:11 +03:00
Alon Girmonsky
7ae81ccc4b Add MySQL protocol to default configuration (#1916)
Closes #1915

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-04-28 15:49:44 +03:00
Serhii Ponomarenko
27111e48d3 🔨 Create dashboard entries-limit helm value (#1914)
* 🔨 Create dashboard entries-limit helm value

* 🔨 Set default value for entries-limit env
2026-04-23 18:20:22 +03:00
Alon Girmonsky
863be8f47a 🔖 Bump the Helm chart version to 53.2.3 (#1912) 2026-04-20 16:39:25 +03:00
Serhii Ponomarenko
9e4059bc4d 🔨 Set nginx proxy-buffer directives (#1909) 2026-04-18 08:07:47 +03:00
Alon Girmonsky
f79885bd35 🔖 Release v53.2.2 (#1908)
* 🔖 Bump the Helm chart version to 53.2.2

* temp

* temp2

* revert back makefile
2026-04-14 01:21:58 -07:00
Volodymyr Stoiko
31129e570a Provide external volume for dissection job (#1905)
* Pass dissection storage configuration

* add dissection storage test

* Allow pvc management

* Use snapshot storage config as default for dissection storage config

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-04-10 09:51:44 -07:00
theechofive
3a1ad64b4c fix: add subPathExpr to worker DaemonSet for shared persistent storage (#1901)
Co-authored-by: Volodymyr Stoiko <me@volodymyrstoiko.com>
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-04-10 09:23:31 -07:00
Alon Girmonsky
fa03da2fd4 Enable MongoDB protocol dissector (#1903)
Add mongodb to the enabled dissectors list and port mapping (27017)
in both Go config defaults and Helm chart values.

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 08:05:13 -07:00
stringsbuilder
4de0ac6abd refactor: replace Split in loops with more efficient SplitSeq and gofmt the code (#1888)
Signed-off-by: stringsbuilder <stringsbuilder@outlook.com>
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-04-06 21:07:50 -07:00
Alon Girmonsky
9b5ac2821f Network RCA skill: update resolution tools to list_workloads/list_ips (#1887)
Replace deprecated resolve_workload/resolve_ip references with the new
list_workloads and list_ips tools that support both singular lookup
(name+namespace or IP) and filtered scan (namespace/regex/label filters
against snapshots).

Ref: kubeshark/hub#687

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-04-06 12:40:34 -07:00
Alon Girmonsky
1ba6ed94e0 💄 Improve README with AI skills, KFL semantics, and cloud storage (#1892)
* 💄 Improve README with AI skills, KFL semantics image, and cloud storage

- Add AI Skills section with Network RCA and KFL skills, Claude Code plugin install
- Rename "Network Traffic Indexing" to "Query with API, Kubernetes, and Network Semantics" with new KFL semantics image showing how a single query combines all three layers
- Add cloud storage providers (S3, Azure Blob, GCS) and decrypted TLS to Traffic Retention section
- Update Features table: add AI Skills, KFL query language, cloud storage, delayed indexing

* 🔒 Add encrypted traffic visibility to README "What you can do" section

* 🎨 Update snapshots image in README

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-04-02 18:38:13 -07:00
Alon Girmonsky
4695acb41e 🐛 Fix release-pr Makefile target cleanup and macOS sed compatibility (#1890)
- Fix macOS sed -i requiring empty backup extension argument
- Checkout master after creating kubeshark release PR
- Checkout master in kubeshark.github.io before and after creating helm PR
- Run all kubeshark.github.io operations in a single shell to avoid lost cd context

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-03-31 12:05:21 -07:00
Alon Girmonsky
b80723edfb 🔖 Bump the Helm chart version to 53.2.0 (#1889)
Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-03-31 11:30:42 -07:00
Alon Girmonsky
ddc2e57f12 Network RCA skill: use local timezone instead of UTC (#1880)
* Use local timezone instead of UTC in Network RCA skill output

Add a Timezone Handling section that instructs the agent to detect the
local timezone, present local time as the primary reference with UTC in
parentheses, and convert UTC tool responses before presenting to users.
Update all example timestamps to demonstrate the local+UTC format.

Closes #1879

* Ensure agent proactively starts dissection for workload/API queries

The agent was waiting for dissection to complete without ever starting it.
Add explicit instructions: check dissection status first, start it if
missing, and default to the Dissection route for any non-PCAP question.
Only PCAP-specific requests can skip dissection.

* Translate every API/Kubernetes question into a fresh list_api_calls query

Add "Every Question Is a Query" section: each user prompt with API or
Kubernetes semantics should map to a list_api_calls call with the
appropriate KFL filter. Includes examples of natural language to KFL
translation. Agent should never answer from memory or stale results.

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-03-24 12:03:05 -07:00
Alon Girmonsky
e80fc3319b Revamp README descriptions and structure (#1881)
* Revamp README intro, sections, and descriptions

Rewrite the opening description to focus on indexing and querying.
Replace "What's captured" with actionable "What you can do" bullets.
Add port-forward step and ingress recommendation to Get Started.
Rename and tighten section descriptions: Network Data for AI Agents,
Network Traffic Indexing, Workload Dependency Map, Traffic Retention
& PCAP Export.

* Remove Raw Capture from features table
2026-03-23 08:33:27 -07:00
Volodymyr Stoiko
868b4c1f36 Verify hub/front pods are ready by conditions (#1864)
* Verify hub/front pods are ready by conditions

* log waiting for readiness

* proper sync

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-21 17:33:48 -07:00
Serhii Ponomarenko
c63740ec45 🐛 Fix dissection-control front env logic (#1878) 2026-03-20 08:20:53 -07:00
Alon Girmonsky
10dbedf356 Add KFL and Network RCA skills (#1875)
* Add KFL and Network RCA skills

Introduce the skills/ directory with two Kubeshark MCP skills:

- network-rca: Retrospective traffic analysis via snapshots, dissection,
  KFL queries, PCAP extraction, and trend comparison
- kfl: Complete KFL2 (Kubeshark Filter Language) reference covering all
  supported protocols, variables, operators, and filter patterns

Update CLAUDE.md with skill authoring guidelines, structure conventions,
and the list of available Kubeshark MCP tools.

* Optimize skills and add shared setup reference

- network-rca: cut repeated metaphor, add list_api_calls example response,
  consolidate use cases, remove unbuilt composability section, extract
  setup reference to references/setup.md (409 → 306 lines)
- kfl: merge thin protocol sections, fix map_get inconsistency, add
  negation examples, move capture source to reference doc
- kfl2-reference: add most-commonly-used variables table, add inline
  filter examples per protocol section
- Add skills/README.md with usage and contribution guidelines

* Add plugin infrastructure and update READMEs

- Add .claude-plugin/plugin.json and marketplace.json for Claude Code
  plugin distribution
- Add .mcp.json bundling the Kubeshark MCP configuration
- Update skills/README.md with plugin install, manual install, and
  agent compatibility sections
- Update mcp/README.md with AI skills section and install instructions
- Restructure network-rca skill into two distinct investigation routes:
  PCAP (no dissection, BPF filters, Wireshark/compliance) and
  Dissection (indexed queries, AI-driven analysis, payload inspection)

* Remove CLAUDE.md from tracked files

Content now lives in skills/README.md, mcp/README.md, and the skills themselves.

* Add README to .claude-plugin directory

* Reorder MCP config: default mode first, URL mode for no-kubectl

* Move AI Skills section to top of MCP README

* Reorder manual install: symlink first

* Streamline skills README: focus on usage and contributing

* Enforce KFL skill loading before writing filters

- network-rca: require loading KFL skill before constructing filters,
  suggest installation if unavailable
- kfl: set user-invocable: false (background knowledge skill), strengthen
  description to mandate loading before any filter construction

* Move KFL requirement to top of Dissection route

* Add strict fallback: only use exact examples if KFL skill unavailable

* Add clone step to manual installation

* Use $PWD/kubeshark paths in manual install examples

* Add mkdir before symlinks, simplify paths

* Move prerequisites before installation

---------

Co-authored-by: Alon Girmonsky <alongir@Alons-Mac-Studio.local>
2026-03-18 15:31:32 -07:00
Serhii Ponomarenko
963b3e4ac2 🐛 Add default value for demoModeEnabled (#1872) 2026-03-17 13:22:42 -07:00
Volodymyr Stoiko
b2813e02bd Add detailed docs for kubeshark irsa setup (#1871)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-16 20:29:49 -07:00
Serhii Ponomarenko
707d7351b6 🛂 Demo Portal: Readonly mode / no authn (#1869)
* 🔨 Add snapshots-updating-enabled `front` env

* 🔨 Add snapshots-updating-enabled config

* 🔨 Add demo-enabled `front` env

* 🔨 Add demo-enabled config

* 🔨 Replace `liveConfigMapChangesDisabled` with `demoModeEnabled` flag

* 🐛 Fix dissection-control-enabled env logic

* 🦺 Handle nullish `demoModeEnabled` value
2026-03-16 20:01:18 -07:00
Serhii Ponomarenko
23c86be773 🛂 Control L4 map visibility (helm value) (#1866)
* 🔨 Add `tap.dashboard.clusterWideMapEnabled` helm value

* 🔨 Add cluster-wide-map-enabled `front` env

* 🔨 Add fallback value for `front` env
2026-03-11 15:36:20 -07:00
Alon Girmonsky
3f8a067f9b Update README: Network Observability for SREs & AI Agents (#1861)
* Update README hero: Network Observability for SREs & AI Agents

Rewrite hero section to focus on cluster-wide network data
consolidation and dual access model (AI agents via MCP,
human operators via dashboard).

* Add MCP demo GIF to README hero section

Replace static stream.png with animated MCP demo showing
Claude Code + Kubeshark workflow.

* Reorder README sections and add MCP demo GIF

- Hero description + stream.png first
- Get Started section
- AI-Powered Network Analysis with MCP demo GIF
- L7 API Dissection
- L4/L7 Workload Map
- Traffic Retention
- Features, Install, Contributing, License

* Reference MCP demo GIF by commit SHA for preview

* Update MCP demo GIF reference to assets master
2026-03-09 08:29:52 -07:00
Volodymyr Stoiko
33f5310e8e Add gcs cloudstorage configuration docs (#1862)
* add gcs docs

* add explicit gcs keys

* gcs helm tests

* add iam permissions docs for gcs

* Update gcs docs with exact setup steps for workload identity
2026-03-09 07:48:17 -07:00
Alon Girmonsky
5f2f34e826 Sync helm-chart README with current values.yaml (#1856)
Update configuration table to match actual defaults in values.yaml:

- tap.storageLimit: 5Gi → 10Gi
- tap.capture.dbMaxSize: "" → 500Mi
- tap.resources.sniffer/tracer.limits.memory: 3Gi → 5Gi
- tap.probes.hub/sniffer initialDelaySeconds: 15 → 5
- tap.probes.hub/sniffer periodSeconds: 10 → 5
- tap.dnsConfig.* → tap.dns.* (match yaml tag)
- tap.sentry.enabled: true → false

Add missing entries:
- tap.capture.captureSelf
- tap.delayedDissection.cpu/memory
- tap.packetCapture
- tap.misc.trafficSampleRate
- tap.misc.tcpStreamChannelTimeoutMs

Remove stale KernelMapping text.
2026-03-06 11:52:10 -08:00
Volodymyr Stoiko
f9a5fbbb78 Fix snapshots local storage size (#1859)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-06 08:33:59 -08:00
Volodymyr Stoiko
73f8e3585d Cloud storage explicit config (#1858)
* Add explicit configs

* Add helm unit tests

* fixpipeline

* latest

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-06 08:27:08 -08:00
Alon Girmonsky
a6daefc567 Fix MCP Registry publish by using OIDC auth instead of interactive OAuth (#1857)
mcp-publisher login github uses the device flow (interactive OAuth) which
requires a human to visit a URL - this can never work in CI. Switch to
github-oidc which uses the OIDC token provided by GitHub Actions.
2026-03-06 08:04:26 -08:00
Alon Girmonsky
e6a67cc3b7 🔖 Release v53.1.0 (#1854)
* 🔖 Bump the Helm chart version to 53.1.0

* Fix reviewer username typo: corst -> corest

* Fold release-helm into release-pr for a 2-step workflow

* Update .github/workflows/release-tag.yml

Co-authored-by: Volodymyr Stoiko <me@volodymyrstoiko.com>

---------

Co-authored-by: Volodymyr Stoiko <me@volodymyrstoiko.com>
2026-03-05 08:25:59 -08:00
Alon Girmonsky
eb7dc42b6e Add get_file_url and download_file MCP tools (#1853)
* Reapply "Add get_file_url and download_file MCP tools"

This reverts commit a46f05c4aa.

* Use dedicated HTTP client for file downloads to support large files

The default httpClient has a 30s total timeout that would fail for
large PCAP downloads (up to 10GB). Use a separate client with only
connection-level timeouts (TLS handshake, response headers) so the
body can stream without a deadline.
2026-03-04 09:17:23 -08:00
Volodymyr Stoiko
d266408377 Add snapshots cloud storage (#1852)
* add testing values for helm chart

* Add readme updates for cloud storage

* fixes

* cloud-storage-docs

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-04 08:50:45 -08:00
sunnyraindy
40ae6c626b chore: remove duplicate package import (#1800)
Signed-off-by: sunnyraindy <sunnyraindy@outlook.com>
Co-authored-by: Volodymyr Stoiko <me@volodymyrstoiko.com>
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-03-04 08:39:32 -08:00
Alon Girmonsky
e3283327f9 Add --release-helmChartPath CLI flag for local Helm chart support (#1851)
Allow users to specify a local Helm chart folder via CLI flag or config,
which takes precedence over the KUBESHARK_HELM_CHART_PATH env variable and
the remote Helm repo. Also update nginx proxy config to disable buffering
for better streaming and large snapshot support.
2026-03-04 08:29:04 -08:00
Alon Girmonsky
a46f05c4aa Revert "Add get_file_url and download_file MCP tools"
This reverts commit dbfd17d901.
2026-03-03 15:06:52 -08:00
Alon Girmonsky
dbfd17d901 Add get_file_url and download_file MCP tools
When tools like export_snapshot_pcap return a relative file path,
the MCP client needs a way to resolve it to a full URL or download
the file locally. These two new tools bridge that gap.
2026-03-03 14:54:39 -08:00
Volodymyr Stoiko
95c18b57a4 Use dissection image tag from worker (#1850) 2026-02-25 11:41:50 -08:00
Alon Girmonsky
6fd2e4b1b2 updated gitignore (#1849) 2026-02-18 11:52:13 -08:00
Volodymyr Stoiko
686c7eba54 Adjust nginx config to work with large download/upload snapshots (#1848)
* adjust-nginx

* cleanup

* improve

* streaming
2026-02-18 10:48:57 -08:00
Ilya Gavrilov
1ad61798f6 Set tcp and udp flows timeouts. Default is 20 minutes (#1847)
* Set tcp and udp flows timeouts. Default is 10 minutes

* fix make test
2026-02-17 16:50:13 -08:00
Alon Girmonsky
318b35e785 Update README and fix broken links (#1846)
* Update README with new structure and AI focus

* Update AI section: AI-Powered Root Cause Analysis with agents

* updated links

* added an image to the API context

* some fixes to the readme

* Remove TODO comments - using real images

* Fix broken MCP Registry links in mcp/README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 15:43:04 -08:00
Volodymyr Stoiko
fecf290a25 Rename generic capture to l7 dissection specific config (#1841)
* Rename generic capture to l7 dissection specific config

* upd

* upd flags

* Create `REACT_APP_DISSECTION_ENABLED` env to set initial dissection state

---------

Co-authored-by: Serhii Ponomarenko <116438358+tiptophelmet@users.noreply.github.com>
Co-authored-by: tiptophelmet <serhii.ponomarenko.jobs@gmail.com>
2026-02-11 11:27:37 -08:00
Alon Girmonsky
a01f7bed74 Update README with new structure and AI focus (#1844)
* Update README with new structure and AI focus

* Update AI section: AI-Powered Root Cause Analysis with agents

* updated links

* added an image to the API context

* some fixes to the readme

* Remove TODO comments - using real images
2026-02-10 10:40:48 -08:00
Serhii Ponomarenko
633a17a0e0 🔧 Add REACT_APP_SCRIPTING_HIDDEN front env (#1845)
* 🔧 Add `scripting.enabled` helm value

* 🔧 Add `REACT_APP_SCRIPTING_HIDDEN` front env

* 🔧 Change `REACT_APP_SCRIPTING_HIDDEN` front env
2026-02-09 13:39:33 -08:00
Alon Girmonsky
8fac9a5ad5 Fix MCP Hub API tool call field name (#1842)
The Hub API expects 'name' field but the MCP server was sending 'tool'.
This caused all Hub-forwarded tools (list_l4_flows, get_l4_flow_summary,
list_api_calls, etc.) to fail with 'tool name is required' error.

Local tools like check_kubeshark_status were unaffected as they don't
call the Hub API.
2026-02-09 13:03:51 -08:00
Ilya Gavrilov
76c5eb6b59 Rename flow and full_flow to conn and flow (#1838)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-02-09 13:01:24 -08:00
Alon Girmonsky
482082ba49 Add MCP Registry support with MCPB package format (#1839)
* Add MCP Registry support with MCPB package format
- Update release workflow to create .mcpb artifacts for MCP Registry
- Update server.json to use MCPB registry type with GitHub namespace
- Use io.github.kubeshark/mcp namespace for GitHub authentication
- Add SHA256 placeholders (to be updated after first release)

* Add automated MCP Registry publishing to release workflow
- Add workflow_dispatch trigger with dry_run option for testing
- Add mcp-publish job that runs after release completes
- Generate server.json dynamically with correct version and SHA256 hashes
- Install and run mcp-publisher automatically
- Update static server.json to reference file with placeholders
- Add MCP Registry section to README
The release workflow now automatically publishes to the MCP Registry
when a new version is tagged. No manual steps required.

* Refactor: Extract MCP publishing to separate workflow
- Create mcp-publish.yml that triggers on release:published
- Simplify release.yml to focus on building and releasing
- MCP workflow has its own workflow_dispatch for testing
- Cleaner separation of concerns

* Address PR review feedback

- Update actions/checkout to v4
- Add OIDC permissions for MCP Registry authentication
- Change trigger from release:published to workflow_call
- Release workflow now calls mcp-publish after artifacts are uploaded
- Keep workflow_dispatch for manual testing

* Add mcp-publisher login step before publish
2026-02-09 10:12:41 -08:00
Serhii Ponomarenko
6ae379cbff 🔧 Hide agentic functionality prototype flags (#1840) 2026-02-06 20:33:15 -08:00
Dan Mudge
3f6c62a7e3 move the name of the data colume outside of the tap.tls if statement (#1830)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-02-06 11:46:54 -08:00
Alon Girmonsky
717433badb [3] Add MCP integration test framework (#1834)
* Add MCP (Model Context Protocol) server command

Implement `kubeshark mcp` command that runs an MCP server over stdio,
enabling AI assistants to query Kubeshark's network visibility data.

Features:
- MCP protocol implementation (JSON-RPC 2.0 over stdio)
- Dynamic tool discovery from Hub's /api/mcp endpoint
- Local cluster management tools (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- --url flag for direct connection to existing Kubeshark deployment
- --kubeconfig flag for proxy mode with kubectl
- --allow-destructive flag to enable start/stop operations (safe by default)
- --list-tools flag to display available tools
- --mcp-config flag to generate MCP client configuration
- 5-minute cache TTL for Hub tools/prompts
- Prompts for common analysis tasks

* Address code review comments for MCP implementation

- Add 30s timeout to HTTP client to prevent hanging requests
- Add scanner.Err() check after stdin processing loop
- Close HTTP response bodies to prevent resource leaks
- Add goroutine to wait on started process to prevent zombies
- Simplify polling loop by removing ineffective context check
- Advertise check_kubeshark_status in URL mode (was callable but hidden)
- Update documentation to clarify URL mode only disables start/stop

* Fix lint errors in mcpRunner.go

- Use type conversion instead of struct literals for hubMCPTool -> mcpTool
  and hubMCPPromptArg -> mcpPromptArg (S1016 gosimple)
- Lowercase error string to follow Go conventions (ST1005 staticcheck)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP server unit tests

Comprehensive unit tests for the MCP server implementation:
- Protocol tests (initialize, tools/list, tools/call, prompts/list, prompts/get)
- Tool tests (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- Hub integration tests (tool fetching, caching, prompt handling)
- Error handling tests
- Edge case tests

* Fix MCP unit tests to use correct /tools/call endpoint

- Update all Hub tool tests to use POST /tools/call endpoint instead
  of individual paths like /workloads, /calls, /stats
- Verify arguments in POST body instead of URL query parameters
- Add newMockHubHandler helper for proper Hub endpoint mocking
- Split TestMCP_ToolsList into three tests:
  - TestMCP_ToolsList_CLIOnly: Tests without Hub backend
  - TestMCP_ToolsList_WithDestructive: Tests with destructive flag
  - TestMCP_ToolsList_WithHubBackend: Tests with mock Hub providing tools
- Fix TestMCP_FullConversation to mock Hub MCP endpoint correctly
- Rename URL encoding tests for clarity
- All tests now correctly reflect the implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify MCP unit tests

- Remove section header comments (10 headers)
- Consolidate similar tests using table-driven patterns
- Simplify test assertions with more concise checks
- Combine edge case tests into single test function
- Reduce verbose test structures

Total reduction: 1477 → 495 lines (66%)
All 24 tests still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP integration test framework

Add integration tests that run against a real Kubernetes cluster:
- MCP protocol tests (initialize, tools/list, prompts/list)
- Cluster management tests (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- Full lifecycle test (check -> start -> check -> stop -> check)
- API tools tests (list_workloads, list_api_calls, get_api_stats)

Also includes:
- Makefile targets for running integration tests
- Test helper functions (startMCPSession, cleanupKubeshark, etc.)
- Documentation (README.md, TEMPLATE.md, ISSUE_TEMPLATE.md)

* Address review comments on integration tests

Makefile:
- Use unique temporary files (mktemp) instead of shared /tmp/integration-test.log
  to prevent race conditions when multiple test targets run concurrently
- Remove redundant test-integration-verbose target (test-integration already uses -v)
- Add cleanup (rm -f) for temporary log files

integration/mcp_test.go:
- Capture stderr from MCP server for debugging failures
- Add getStderr() method to mcpSession for accessing captured stderr
- Fix potential goroutine leak by adding return statements after t.Fatalf
- Remove t.Run subtests in TestMCP_APIToolsRequireKubeshark to clarify
  sequential execution with shared session
- Fix benchmark to use getKubesharkBinary helper for consistency
- Add Kubernetes cluster check to benchmark (graceful skip)
- Add proper error handling for pipe creation in benchmark
- Remove unnecessary bytes import workaround (now actually used for stderr)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify and clean up MCP integration tests

- Remove unrelated L4 viewer files (1239 lines)
- Remove template/issue documentation files (419 lines)
- Trim README to essential content only
- Remove TEMPLATE comments from common_test.go
- Add initialize() helper to reduce test boilerplate
- Add hasKubernetesCluster() helper for benchmarks
- Simplify all test functions with consistent patterns

Total reduction: 2964 → 866 lines (71%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:37:12 -08:00
Alon Girmonsky
a973d6916d [2] Add MCP server unit tests (#1833)
* Add MCP (Model Context Protocol) server command

Implement `kubeshark mcp` command that runs an MCP server over stdio,
enabling AI assistants to query Kubeshark's network visibility data.

Features:
- MCP protocol implementation (JSON-RPC 2.0 over stdio)
- Dynamic tool discovery from Hub's /api/mcp endpoint
- Local cluster management tools (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- --url flag for direct connection to existing Kubeshark deployment
- --kubeconfig flag for proxy mode with kubectl
- --allow-destructive flag to enable start/stop operations (safe by default)
- --list-tools flag to display available tools
- --mcp-config flag to generate MCP client configuration
- 5-minute cache TTL for Hub tools/prompts
- Prompts for common analysis tasks

* Address code review comments for MCP implementation

- Add 30s timeout to HTTP client to prevent hanging requests
- Add scanner.Err() check after stdin processing loop
- Close HTTP response bodies to prevent resource leaks
- Add goroutine to wait on started process to prevent zombies
- Simplify polling loop by removing ineffective context check
- Advertise check_kubeshark_status in URL mode (was callable but hidden)
- Update documentation to clarify URL mode only disables start/stop

* Fix lint errors in mcpRunner.go

- Use type conversion instead of struct literals for hubMCPTool -> mcpTool
  and hubMCPPromptArg -> mcpPromptArg (S1016 gosimple)
- Lowercase error string to follow Go conventions (ST1005 staticcheck)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP server unit tests

Comprehensive unit tests for the MCP server implementation:
- Protocol tests (initialize, tools/list, tools/call, prompts/list, prompts/get)
- Tool tests (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- Hub integration tests (tool fetching, caching, prompt handling)
- Error handling tests
- Edge case tests

* Fix MCP unit tests to use correct /tools/call endpoint

- Update all Hub tool tests to use POST /tools/call endpoint instead
  of individual paths like /workloads, /calls, /stats
- Verify arguments in POST body instead of URL query parameters
- Add newMockHubHandler helper for proper Hub endpoint mocking
- Split TestMCP_ToolsList into three tests:
  - TestMCP_ToolsList_CLIOnly: Tests without Hub backend
  - TestMCP_ToolsList_WithDestructive: Tests with destructive flag
  - TestMCP_ToolsList_WithHubBackend: Tests with mock Hub providing tools
- Fix TestMCP_FullConversation to mock Hub MCP endpoint correctly
- Rename URL encoding tests for clarity
- All tests now correctly reflect the implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify MCP unit tests

- Remove section header comments (10 headers)
- Consolidate similar tests using table-driven patterns
- Simplify test assertions with more concise checks
- Combine edge case tests into single test function
- Reduce verbose test structures

Total reduction: 1477 → 495 lines (66%)
All 24 tests still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:30:50 -08:00
Alon Girmonsky
2ccd716a68 Add MCP registry metadata for official registry submission (#1835)
* Add MCP (Model Context Protocol) server command

Implement `kubeshark mcp` command that runs an MCP server over stdio,
enabling AI assistants to query Kubeshark's network visibility data.

Features:
- MCP protocol implementation (JSON-RPC 2.0 over stdio)
- Dynamic tool discovery from Hub's /api/mcp endpoint
- Local cluster management tools (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- --url flag for direct connection to existing Kubeshark deployment
- --kubeconfig flag for proxy mode with kubectl
- --allow-destructive flag to enable start/stop operations (safe by default)
- --list-tools flag to display available tools
- --mcp-config flag to generate MCP client configuration
- 5-minute cache TTL for Hub tools/prompts
- Prompts for common analysis tasks

* Address code review comments for MCP implementation

- Add 30s timeout to HTTP client to prevent hanging requests
- Add scanner.Err() check after stdin processing loop
- Close HTTP response bodies to prevent resource leaks
- Add goroutine to wait on started process to prevent zombies
- Simplify polling loop by removing ineffective context check
- Advertise check_kubeshark_status in URL mode (was callable but hidden)
- Update documentation to clarify URL mode only disables start/stop

* Fix lint errors in mcpRunner.go

- Use type conversion instead of struct literals for hubMCPTool -> mcpTool
  and hubMCPPromptArg -> mcpPromptArg (S1016 gosimple)
- Lowercase error string to follow Go conventions (ST1005 staticcheck)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP server unit tests

Comprehensive unit tests for the MCP server implementation:
- Protocol tests (initialize, tools/list, tools/call, prompts/list, prompts/get)
- Tool tests (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- Hub integration tests (tool fetching, caching, prompt handling)
- Error handling tests
- Edge case tests

* Fix MCP unit tests to use correct /tools/call endpoint

- Update all Hub tool tests to use POST /tools/call endpoint instead
  of individual paths like /workloads, /calls, /stats
- Verify arguments in POST body instead of URL query parameters
- Add newMockHubHandler helper for proper Hub endpoint mocking
- Split TestMCP_ToolsList into three tests:
  - TestMCP_ToolsList_CLIOnly: Tests without Hub backend
  - TestMCP_ToolsList_WithDestructive: Tests with destructive flag
  - TestMCP_ToolsList_WithHubBackend: Tests with mock Hub providing tools
- Fix TestMCP_FullConversation to mock Hub MCP endpoint correctly
- Rename URL encoding tests for clarity
- All tests now correctly reflect the implementation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify MCP unit tests

- Remove section header comments (10 headers)
- Consolidate similar tests using table-driven patterns
- Simplify test assertions with more concise checks
- Combine edge case tests into single test function
- Reduce verbose test structures

Total reduction: 1477 → 495 lines (66%)
All 24 tests still pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP integration test framework

Add integration tests that run against a real Kubernetes cluster:
- MCP protocol tests (initialize, tools/list, prompts/list)
- Cluster management tests (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- Full lifecycle test (check -> start -> check -> stop -> check)
- API tools tests (list_workloads, list_api_calls, get_api_stats)

Also includes:
- Makefile targets for running integration tests
- Test helper functions (startMCPSession, cleanupKubeshark, etc.)
- Documentation (README.md, TEMPLATE.md, ISSUE_TEMPLATE.md)

* Address review comments on integration tests

Makefile:
- Use unique temporary files (mktemp) instead of shared /tmp/integration-test.log
  to prevent race conditions when multiple test targets run concurrently
- Remove redundant test-integration-verbose target (test-integration already uses -v)
- Add cleanup (rm -f) for temporary log files

integration/mcp_test.go:
- Capture stderr from MCP server for debugging failures
- Add getStderr() method to mcpSession for accessing captured stderr
- Fix potential goroutine leak by adding return statements after t.Fatalf
- Remove t.Run subtests in TestMCP_APIToolsRequireKubeshark to clarify
  sequential execution with shared session
- Fix benchmark to use getKubesharkBinary helper for consistency
- Add Kubernetes cluster check to benchmark (graceful skip)
- Add proper error handling for pipe creation in benchmark
- Remove unnecessary bytes import workaround (now actually used for stderr)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify and clean up MCP integration tests

- Remove unrelated L4 viewer files (1239 lines)
- Remove template/issue documentation files (419 lines)
- Trim README to essential content only
- Remove TEMPLATE comments from common_test.go
- Add initialize() helper to reduce test boilerplate
- Add hasKubernetesCluster() helper for benchmarks
- Simplify all test functions with consistent patterns

Total reduction: 2964 → 866 lines (71%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add MCP registry metadata for official registry submission

Add metadata files for submitting Kubeshark MCP server to the official
MCP registry at registry.modelcontextprotocol.io:

- mcp/server.json: Registry metadata with tools, prompts, and configuration
- mcp/README.md: MCP server documentation and usage guide

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 10:39:42 -08:00
Alon Girmonsky
0bbbb473ea [1] Add MCP (Model Context Protocol) server command (#1832)
* Add MCP (Model Context Protocol) server command

Implement `kubeshark mcp` command that runs an MCP server over stdio,
enabling AI assistants to query Kubeshark's network visibility data.

Features:
- MCP protocol implementation (JSON-RPC 2.0 over stdio)
- Dynamic tool discovery from Hub's /api/mcp endpoint
- Local cluster management tools (check_kubeshark_status, start_kubeshark, stop_kubeshark)
- --url flag for direct connection to existing Kubeshark deployment
- --kubeconfig flag for proxy mode with kubectl
- --allow-destructive flag to enable start/stop operations (safe by default)
- --list-tools flag to display available tools
- --mcp-config flag to generate MCP client configuration
- 5-minute cache TTL for Hub tools/prompts
- Prompts for common analysis tasks

* Address code review comments for MCP implementation

- Add 30s timeout to HTTP client to prevent hanging requests
- Add scanner.Err() check after stdin processing loop
- Close HTTP response bodies to prevent resource leaks
- Add goroutine to wait on started process to prevent zombies
- Simplify polling loop by removing ineffective context check
- Advertise check_kubeshark_status in URL mode (was callable but hidden)
- Update documentation to clarify URL mode only disables start/stop

* Fix lint errors in mcpRunner.go

- Use type conversion instead of struct literals for hubMCPTool -> mcpTool
  and hubMCPPromptArg -> mcpPromptArg (S1016 gosimple)
- Lowercase error string to follow Go conventions (ST1005 staticcheck)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 10:22:49 -08:00
Serhii Ponomarenko
d012ea89b6 🔨 Enable L4 flow-full dissectors (#1831)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-02-05 12:14:51 -08:00
Volodymyr Stoiko
0f1c9c52ea Add captureSelf flag to enable/disable kubeshark traffic capture (#1829)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-02-04 08:49:01 -08:00
Volodymyr Stoiko
f3a0d35485 Make cloud-api-url configurable (#1827) 2026-02-03 10:11:43 -08:00
Volodymyr Stoiko
d6631e8565 Remove automated release to brew-core (#1825) 2026-01-26 17:31:47 -08:00
Alon Girmonsky
1669680d10 🔖 Bump the Helm chart version to 52.12.0 2026-01-25 17:54:24 -08:00
Alon Girmonsky
19389fcba7 Bring back community license. (#1822) 2026-01-23 14:49:38 -08:00
Volodymyr Stoiko
1b027153e3 Increase dissector image limit (#1823)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-01-21 19:52:45 -08:00
Volodymyr Stoiko
77d16e73e8 Migrate kubehq.com to kubeshark.com domain (#1824)
* Update labels

* Update kubeshark API url

* Update other domains

* comments

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2026-01-21 19:23:50 -08:00
Volodymyr Stoiko
a73c904a9b Use console.kubeshark.com instead of kubehq (#1821) 2026-01-21 09:18:47 -08:00
Alon Girmonsky
8f3f136be6 Update README.md (#1820) 2026-01-20 17:16:02 -08:00
Alon Girmonsky
897aa44965 🔖 Bump the Helm chart version to 52.11.7 2026-01-15 13:23:40 -08:00
Alon Girmonsky
57093e8a25 Updated complementary license (#1819)
* Updated complementary license
To Feb 14th.

* Update README.md

shout out
2026-01-15 13:17:15 -08:00
Volodymyr Stoiko
1fd9dffc60 Add delayed dissection controller configuration (#1818)
* Allow managing jobs in kubeshark namespaces

* Extend with dissector image

* make dissection job resources configurable
2026-01-06 11:56:53 -08:00
Alon Girmonsky
3b315eb89f Update CONTRIBUTING.md (#1817) 2025-12-28 13:38:08 +02:00
Alon Girmonsky
6645e23704 🔖 Bump the Helm chart version to 52.11.0 2025-12-16 11:49:37 -08:00
Alon Girmonsky
b7190162ec In preparation for v200 (#1815)
* In preparation for v200

* updated README

* Enable raw capture

* changed 0.0.0.0 to 127.0.0.1
as 0.0.0.0 is insecure address

* added tip: kubeshark proxy

* added new TCP/UDP connection dissectors
Set API2 as the default

* increased storageLimit per worker.

* Updated makefile

* updated the complementary license
to the end of Jan 2026.

* readme touch ups

* Updated snapshot image

* updated license
removed dashboard subproject
2025-12-16 11:44:02 -08:00
Alon Girmonsky
9570b2e317 Update README.md (#1814) 2025-12-05 08:27:25 -08:00
Serhii Ponomarenko
b98113a2b5 🔨 Create raw-capture-enabled front env (#1813) 2025-12-01 16:02:19 -08:00
Alon Girmonsky
9724a0c279 🔖 Bump the Helm chart version to 52.10.3 2025-11-28 17:12:22 -08:00
Alon Girmonsky
47ac96a71b Adding a default license (#1812) 2025-11-28 17:06:48 -08:00
Serhii Ponomarenko
4dea643781 🚑 Use www.kubehq.com for links (#1809) 2025-11-26 08:14:40 -08:00
Alon Girmonsky
03a53ad6d5 🔖 Bump the Helm chart version to 52.10.0 2025-11-25 11:49:50 -08:00
Alon Girmonsky
a12a5aec19 🔖 Bump the Helm chart version to 52.10.0 2025-11-25 11:40:17 -08:00
Volodymyr Stoiko
4931116881 Update kubeshark.co references (#1807) 2025-11-25 10:44:21 -08:00
Serhii Ponomarenko
eb9a82962f 🚑 Migrate from kubeshark.co to kubehq.com (#1805)
* 🚑 Migrate to `kubehq.com` in helm values

* 🚑 Migrate to `kubehq.com` in cloud-api-url envs

* 🚑 Migrate to `kubehq.com` in manifest label keys

* 🚑 Migrate to `kubehq.com` in `Chart.yaml`

* 🚑 Migrate to `kubehq.com` in helm-chart notes/readme
2025-11-25 10:22:51 -08:00
Alon Girmonsky
bd10e035ff Adding Slack Support (#1804)
Adding Slack Support Channel
2025-11-25 07:50:53 -08:00
Volodymyr Stoiko
25832ce596 Make host-network in worker daemonset configurable (#1803)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-11-24 16:43:48 -08:00
Serhii Ponomarenko
38a13d19e1 Revert "🔨 Add -save-objects-pcaps worker command flag (#1794)" (#1802)
This reverts commit dcb84e0520.
2025-11-20 08:41:32 -08:00
Volodymyr Stoiko
a7b9e09f2b Add volume for snapshots in hub (#1801)
* Add hub snapshots volume

* Add snapshot limit into env

* fix
2025-11-17 10:45:41 -08:00
Serhii Ponomarenko
dcb84e0520 🔨 Add -save-objects-pcaps worker command flag (#1794)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-11-07 08:14:54 -08:00
Ilya Gavrilov
773fefae21 Set default dbMaxSize to 500Mi (#1796) 2025-11-06 11:41:35 -08:00
Alon Girmonsky
d640128e85 🔖 Bump the Helm chart version to 52.9.0 2025-10-03 16:30:55 +02:00
Alon Girmonsky
7dcacf14f2 Removed the !error && !dns and disabled support chat option by default (#1792)
* removed the !error && !dns

* removed the default "!dns && !error"

* changed support option to false
2025-10-03 16:26:50 +02:00
Volodymyr Stoiko
fabf30c039 Add note about setting license in helm notes (#1791) 2025-09-30 16:09:05 -07:00
Volodymyr Stoiko
e55b62491a Add raw capture config parameters (#1789)
* Add raw capture config parameters

* upd

* upd
2025-09-30 08:26:42 -07:00
Volodymyr Stoiko
f5167cbb2a Pass db storage size and ration to calculate for badger db (#1788)
* Pass db storage size and ration to calculate for badger db

* Use badger max db size option
2025-09-25 08:17:21 -07:00
Serhii Ponomarenko
349d8b07df 🔨 Add tap.dashboard.streamingType helm value (#1783)
* 🔨 Add `tap.dashboard.streamingType` helm value

* 🔨 Add `streamingType` to tap config

* 🔨 Adjust `REACT_APP_STREAMING_TYPE` front env value

* 🔨 Use default empty string for `streamingType` value
2025-09-02 10:43:38 -07:00
Serhii Ponomarenko
88f43b94d9 🔨 Add tap.ingress.path helm value (#1782)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-08-26 13:01:55 -07:00
Volodymyr Stoiko
cf867fe701 Do not create hostroot volume if no tracer deployed (#1780)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-08-26 13:01:13 -07:00
Volodymyr Stoiko
635fcabecd Treat 0 value as 0s for dorman timeout (#1781) 2025-08-26 13:00:01 -07:00
Alon Girmonsky
099b79f3ce 🔖 Bump the Helm chart version to 52.8.1 2025-08-12 12:57:39 -07:00
Volodymyr Stoiko
56b936b8b8 Add stopAfter option to disable capture when inactive (#1778)
* Add stopAfter option to disable capture when inactive

* Use 5m dorman

* Add capture stop after flag in hub
2025-08-12 11:23:16 -07:00
Alon Girmonsky
352484b5f6 🔖 Bump the Helm chart version to 52.8.0 2025-07-28 12:45:45 -07:00
Volodymyr Stoiko
eee3030410 Add priority class configuration for Kubeshark components (#1775)
* Add priority class into templates

* upd readme

* upd

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-07-28 12:18:45 -07:00
Volodymyr Stoiko
5231546210 CVE-2025-53547: Update helm to latest (#1774)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-07-28 12:17:53 -07:00
Serhii Ponomarenko
d845bb18a3 🔨 Add api2BetaEnabled helm value (#1770)
* 🔨 Add `api2BetaEnabled` helm value

* 🔨 Change `api2BetaEnabled` to `betaEnabled`

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-07-28 12:17:08 -07:00
Alon Girmonsky
abee96a863 docs-changes (#1768)
* Update README.md

* Update README.md

* added pcap recording image
2025-07-28 12:12:35 -07:00
cloudclaim
efe6b0e7b7 chore: fix some minor issues in the comments (#1767)
Signed-off-by: cloudclaim <824973921@qq.com>
2025-07-28 12:10:50 -07:00
Volodymyr Stoiko
bedecdb080 Fix bugs in helm chart (#1765) 2025-06-18 08:45:17 -07:00
Alon Girmonsky
c2d10f8cfa 🔖 Bump the Helm chart version to 52.7.8 2025-06-16 14:28:45 -07:00
Alon Girmonsky
161a525b67 updated dry release target 2025-06-16 14:24:46 -07:00
Alon Girmonsky
33353ef21e added back online support (#1764) 2025-06-16 13:14:28 -07:00
Alon Girmonsky
c751a8a6ad enable support 2025-06-12 13:28:50 -07:00
Volodymyr Stoiko
8c9473626e Use chart minor version for dockertag (#1762)
* Revert "Set tap.docker.tag to minor version of release (#1761)"

This reverts commit 6d2b0676f6.

* Fix condition for default image tag
2025-06-04 14:24:23 -07:00
Alon Girmonsky
1d8fa774d3 🔖 Bump the Helm chart version to 52.7.7 2025-06-03 10:11:52 -07:00
Alon Girmonsky
3be80ddf82 🔖 Bump the Helm chart version to 52.7.5 2025-06-03 08:33:29 -07:00
Volodymyr Stoiko
6d2b0676f6 Set tap.docker.tag to minor version of release (#1761)
* Set tap.docker.tag to minor version of release

* Always set v for docker tag
2025-06-03 08:20:41 -07:00
Alon Girmonsky
131b8013ea 🔖 Bump the Helm chart version to 52.7.3 2025-06-02 20:40:44 -07:00
Alon Girmonsky
a18ccac258 Merge branch 'master' of github.com:kubeshark/kubeshark 2025-06-02 13:16:29 -07:00
Alon Girmonsky
2e75d0f2ab disabled sctp by default as it is part of debug protocols. 2025-06-02 13:15:57 -07:00
Alon Girmonsky
398a4b9efc disable watchdog by default (#1759) 2025-06-02 13:04:21 -07:00
Ilya Gavrilov
f9dd99af1b eBPF capture didn't work in case of persistent storage (#1757)
* cleanup data directory in init container

* cleanup data directory in init container

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-05-29 18:27:17 -07:00
Volodymyr Stoiko
ed0fb34888 Add secret names to inject env variables from (#1756)
* Add secrets for inject into hub deployment

* Update notes

* upd

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-05-29 18:24:46 -07:00
Volodymyr Stoiko
a4b0138abe Use full semver version in Chart.yaml (#1754)
* Use full version

* revert

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-05-14 08:27:14 -07:00
Alon Girmonsky
7dcd9eee95 Incerased storage limit from 500Mi to 5Gi (#1755) 2025-05-12 10:34:58 -07:00
Ilya Gavrilov
95213b344d Add kubeshark_dropped_chunks_total metric description (#1753) 2025-05-12 10:27:08 -07:00
Alon Girmonsky
df1628e1a4 🔖 Bump the Helm chart version to 52.7.0 2025-04-16 12:28:32 -07:00
M. Mert Yildiran
43a410b9dd Add --config-path flag to root command (#1744)
* Add `--config-path` flag to root command

* Use `filepath.Abs`

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-04-16 10:28:21 -07:00
Volodymyr Stoiko
7618795fdf Add optional gitops mode (#1748) 2025-04-16 10:18:53 -07:00
Volodymyr Stoiko
4ca9bc8fc0 Run cleanup program instead of kubectl (#1745) 2025-04-16 09:07:31 -07:00
Alon Girmonsky
9775a70722 disable syscall by default as it is a significant (#1742)
resource consuming
2025-04-10 09:43:42 -07:00
Volodymyr Stoiko
1218386638 Decrease initial delay seconds (#1736)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-04-08 13:33:10 -07:00
Volodymyr Stoiko
2bee926b4b Add kubeshark cm and secret -default suffix (#1704)
* Add kubeshark cm and secret -default suffix

* Add cleanup job

* Add cleanup job

* update cleanup

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-04-08 13:24:34 -07:00
Alon Girmonsky
ac5bf9b276 Make changes in default values (#1735)
* Disable Intercom support by default.
Support can be enabled using a helm flag.

* updated the license notification
as a result of a successful helm installation.

* GenAI assistant enabled by default
2025-04-07 08:47:37 -07:00
Volodymyr Stoiko
59026d4ad4 Add pvc volumeMode (#1739)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-04-07 08:25:27 -07:00
Serhii Ponomarenko
25ecc18d39 🔨 Add default value for Dex node selector terms (#1740) 2025-04-07 08:23:04 -07:00
Serhii Ponomarenko
a6eabbbdee 🔨 Add tap.auth.dexOidc.bypassSslCaCheck flag (#1737)
* 🔨 Add `tap.auth.dexOidc.bypassSslCaCheck` flag

* 📝 Update docs for Dex SSL CA bypass

* 🔨 Bring back deleted Dex node-selector-terms
2025-04-04 10:07:02 -07:00
Volodymyr Stoiko
a914733078 Allow reading logs (#1734)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-04-01 13:29:04 -07:00
Serhii Ponomarenko
59ef0f8f80 🔨 Add tap.dashboard.completeStreamingEnabled flag (#1733) 2025-04-01 13:08:46 -07:00
Volodymyr Stoiko
3c13a8d96b Exit properly from scripts command (#1731)
* Fix scripts command exit

* Switch to debug
2025-03-31 13:04:18 -07:00
Alon Girmonsky
dc50ef48fd 🔖 Bump the Helm chart version to 52.6.0 2025-03-24 15:03:27 -07:00
Serhii Ponomarenko
453d27af43 🔨 Create tap.routing.front.basePath flag (#1726)
* 🔨 Add `tap.routing.front.basePath` helm value

* 🔨 Use `tap.routing.front.basePath` to adjust nginx blocks

* 🔨 Set `front` base path to empty string

* 📝 Update `front` base path docs

* 📝 Add `front` base path example

* 📝 Add base-path to Kubeshark URL in instructions

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-03-24 14:23:41 -07:00
Alon Girmonsky
c95d63feb0 Sentry Enabled By Default (#1721)
* Update values.yaml

Enable Sentry by default.

* Update README.md
2025-03-24 14:09:58 -07:00
Serhii Ponomarenko
f85c7dfb4b OIDC support (Dex IdP) (#1722)
* 🔧 Create dex config-map

* 🔧 Create dex deployment

* 🔧 Create dex service

* 🔧 Create dex network policy

* 🔧 Create dex network policy

* 🔧 Add dex node selector terms

* 🔧 Add a kubeshark-hub static client to dex config

* 🐛 Use correct redirect URI for `kubeshark-hub` client

* 🎨 Remove unused/commented dex config options

* 🔨 Create a helper template to pick Kubeshark client secret

* 🔧 Adjust front deployment env to allow `dex` auth type

* 🔧 Adjust configmap to allow `dex` auth type

* 🔧 Create k8s secret to store dex yaml config

* 🔧 Mount dex-yaml-conf secret into `dex-config.yaml`

* 🔥 Remove sample env var

* 🔧 Create k8s config keys for Dex expiry settings

* 🔧 Create k8s secret key for Dex client secret

* 🔧 Deploy Dex resources if Dex auth is enabled

* 🔧 Move `oauth2StateParamExpiry` under `customSettings`

* 📝 Add basic helm-values docs to set up Dex auth

*  Separate Dex OIDC app settings from configuration

* 📝 Update Dex documentation

* 📝 Update Dex IdP documentation

* 🦺 Add fallback value for OIDC issuer config

* 🦺 Add fallback values for OIDC client ID/secret

* 📝 Update Dex IdP documentation

* 📝 Update Dex IdP documentation

* 📝 Add reference to OIDC docs at `docs.kubeshark.co`

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-03-24 14:05:38 -07:00
Volodymyr Stoiko
0386e57906 Add watchdog option (#1723)
* add watchdog

* Enable watchdog on sniffer
2025-03-24 11:02:57 -07:00
Alon Girmonsky
529ca63a47 Update RELEASE.md.TEMPLATE
removed legacy
2025-03-01 22:23:24 +02:00
Alon Girmonsky
eec4404038 🔖 Bump the Helm chart version to 52.5.0 2025-03-01 22:00:24 +02:00
Volodymyr Stoiko
e47a665d68 Update structs and docs (#1710)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-02-21 09:07:17 -08:00
Serhii Ponomarenko
f656acea64 🔧 Add aiAssistantEnabled helm value (#1717)
* 🔧 Add `aiAssistantEnabled` helm value

* 🐛 Add quotes to `AI_ASSISTANT_ENABLED` config val

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-02-21 08:53:27 -08:00
Serhii Ponomarenko
000fb91461 🔧 Enable BPF-override on tap.packetCapture: af_packet (#1712)
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-02-20 17:34:54 -08:00
bogdanvbalan
3c8ee11216 Update name of merged file (#1716)
Co-authored-by: bogdan.balan1 <bogdanvalentin.balan@1nce.com>
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-02-20 17:30:43 -08:00
Serhii Ponomarenko
631e5f2d24 🔨 Add demoModeEnabled helm value (#1714)
* 🔨 Add `demoModeEnabled` helm value

* 🐛 Fix `demoModeEnabled` ternary expressions

* 🦺 Check `demoModeEnabled` existence
2025-02-20 17:25:58 -08:00
Alon Girmonsky
95d6655af6 finished templating tap.mountBpf option. (#1711) 2025-02-12 12:28:52 -08:00
Alon Girmonsky
62912d248d for mac os compatibility 2025-02-10 13:53:42 -08:00
Alon Girmonsky
be8136687b 🔖 Bump the Helm chart version to 52.4.0 2025-02-05 12:15:19 -08:00
Volodymyr Stoiko
3d4606d439 Worker component security context refactoring (#1707)
* Add new security context config

* Fine-grained template for securityContext

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-02-03 13:38:41 -08:00
Ilya Gavrilov
46ca7e3ad7 Remove init container; remove -disable-ebpf option (#1706)
* Remove init container; remove -disable-ebpf option

* Remove init container; remove -disable-ebpf option
2025-02-03 08:58:32 -08:00
Alon Girmonsky
e9796bfb24 Readme updated (#1705)
* Update README.md

* Update README.md
2025-01-29 14:05:00 -08:00
Volodymyr Stoiko
ce7913ce2e Fix pull secret aligning (#1703)
* Fix pull secret aligning

* align
2025-01-29 08:34:43 -08:00
bogdanvbalan
8f6ef686de Refactor and simplify pcapdump logic (#1701)
* Fix spammy logs

* Fix err related to value missing from pcap config

* Test target dir only when provided

* Improve consistency of error handling

* Remove obsolete code

---------

Co-authored-by: bogdan.balan1 <bogdanvalentin.balan@1nce.com>
2025-01-27 13:42:59 -08:00
M. Mert Yildiran
f2e60cdee1 Add PortMapping to TapConfig for port number based dissector prioritization (#1700) 2025-01-25 12:10:53 -08:00
Alon Girmonsky
67aa1dac39 Automatic patch updates
Update Makefile to include the Minor version in the Chart.yaml
in support for automatic patch updates.
2025-01-24 14:54:19 -08:00
Alon Girmonsky
818a9e2bec Moving to eBPF as a default packet capture method.
Making default packet capture method eBPF, defaulting to AF_PACKET in case eBPF is not available
2025-01-24 14:24:02 -08:00
Alon Girmonsky
7eb35a4e11 🔖 Bump the Helm chart version to 52.3.96 2025-01-24 11:42:47 -08:00
Alon Girmonsky
858864e7bc Changed two errors to warnings. 2025-01-24 09:59:43 -08:00
Volodymyr Stoiko
ad10212ba5 Add dns config (#1698)
* Add dnsconfig

* Update templates

* Add dns configuration values

* readme
2025-01-24 09:14:08 -08:00
Alon Girmonsky
0e3f137a69 add diameter protocol support (#1696) 2025-01-22 15:57:46 -08:00
Volodymyr Stoiko
ef17eb9fbe Make node selector component specific (#1694)
* Make node selector component specific

* Update templates

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-01-22 12:50:17 -08:00
Alon Girmonsky
aa7c8f36f5 added -disable-tracer option (#1695)
to the worker daemon set, when `tap.tls=false` is set.
2025-01-22 12:32:05 -08:00
bogdanvbalan
c92f509863 #528 Remove pcap src from configMap (#1693)
* Remove pcap src from configMap

* change folder name

keep it simple and short

---------

Co-authored-by: bogdan.balan1 <bogdanvalentin.balan@1nce.com>
Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-01-22 10:10:44 -08:00
Serhii Ponomarenko
0d5bbd53aa 🔧 Add helm variable to disable live config-map user actions (#1689)
* 🔧 Add helm variable to disable live config-map user actions

* 🐛 Fix ternary for `PRESET_FILTERS_CHANGING_ENABLED` config

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-01-18 13:15:46 +02:00
bogdanvbalan
0b00b1846b Fix error on getting namespaces for pcap target files (#1691)
* Fix err when using dest arg

* Add debug

* Debug pcap download

* Fix ns bug

* Fix namespace bug

* Clean debug leftovers

---------

Co-authored-by: bogdan.balan1 <bogdanvalentin.balan@1nce.com>
2025-01-18 13:04:54 +02:00
bogdanvbalan
507099c1ec Fix err when using dest arg (#1688)
Co-authored-by: bogdan.balan1 <bogdanvalentin.balan@1nce.com>
2025-01-16 10:19:18 +02:00
Alon Girmonsky
aca3f4ad44 🔖 Bump the Helm chart version to 52.3.95 2025-01-11 15:53:08 +01:00
Volodymyr Stoiko
f9c66df528 Update worker liveness/readiness config (#1684)
* Increase worker init delay to 30s

* Update values

* fix

* Make probe values configurable

* upd

---------

Co-authored-by: Alon Girmonsky <1990761+alongir@users.noreply.github.com>
2025-01-08 13:09:51 -08:00
Alon Girmonsky
1d572e6bff support new radius protocol (#1682) 2025-01-08 13:05:57 -08:00
Alon Girmonsky
46ad335446 updated the notes (#1681) 2025-01-06 18:42:17 -08:00
88 changed files with 11593 additions and 1972 deletions

33
.claude-plugin/README.md Normal file
View File

@@ -0,0 +1,33 @@
# Kubeshark Claude Code Plugin
This directory contains the [Claude Code plugin](https://docs.anthropic.com/en/docs/claude-code/plugins) configuration for Kubeshark.
## What's here
| File | Purpose |
|------|---------|
| `plugin.json` | Plugin manifest — name, version, description, metadata |
| `marketplace.json` | Marketplace index — allows discovery via `/plugin marketplace add` |
## Installing the plugin
```
/plugin marketplace add kubeshark/kubeshark
/plugin install kubeshark
```
This loads the Kubeshark AI skills and MCP configuration. Skills appear as
`/kubeshark:network-rca` and `/kubeshark:kfl`.
## What the plugin includes
- **Skills** from [`skills/`](../skills/) — network root cause analysis and KFL filter expertise
- **MCP configuration** from [`.mcp.json`](../.mcp.json) — connects to the Kubeshark MCP server
## Local development
Test the plugin without installing:
```bash
claude --plugin-dir /path/to/kubeshark
```

View File

@@ -0,0 +1,15 @@
{
"name": "kubeshark",
"description": "Kubeshark network observability skills for Kubernetes",
"plugins": [
{
"name": "kubeshark",
"description": "Network observability skills powered by Kubeshark MCP — root cause analysis, KFL traffic filtering, snapshot forensics, PCAP extraction.",
"source": {
"source": "github",
"owner": "kubeshark",
"repo": "kubeshark"
}
}
]
}

View File

@@ -0,0 +1,24 @@
{
"name": "kubeshark",
"version": "1.0.0",
"description": "Kubernetes network observability skills powered by Kubeshark MCP. Root cause analysis, traffic filtering, snapshot forensics, PCAP extraction, and more.",
"author": {
"name": "Kubeshark",
"url": "https://kubeshark.com"
},
"homepage": "https://kubeshark.com",
"repository": "https://github.com/kubeshark/kubeshark",
"license": "Apache-2.0",
"keywords": [
"kubeshark",
"kubernetes",
"network",
"observability",
"traffic",
"mcp",
"rca",
"pcap",
"kfl",
"ebpf"
]
}

201
.github/workflows/mcp-publish.yml vendored Normal file
View File

@@ -0,0 +1,201 @@
name: MCP Registry Publish
on:
workflow_call:
inputs:
release_tag:
description: 'Release tag to publish (e.g., v52.13.0)'
type: string
required: true
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run - generate server.json but skip actual publishing'
type: boolean
default: true
release_tag:
description: 'Release tag to publish (e.g., v52.13.0)'
type: string
required: true
jobs:
mcp-publish:
name: Publish to MCP Registry
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC authentication with MCP Registry
contents: read # Required for checkout
steps:
- name: Check out the repo
uses: actions/checkout@v4
- name: Determine version
id: version
shell: bash
run: |
# inputs.release_tag works for both workflow_call and workflow_dispatch
VERSION="${{ inputs.release_tag }}"
echo "tag=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Publishing MCP server for version: ${VERSION}"
- name: Download SHA256 files from release
shell: bash
run: |
VERSION="${{ steps.version.outputs.tag }}"
mkdir -p bin
echo "Downloading SHA256 checksums from release ${VERSION}..."
for platform in darwin_arm64 darwin_amd64 linux_arm64 linux_amd64 windows_amd64; do
url="https://github.com/kubeshark/kubeshark/releases/download/${VERSION}/kubeshark-mcp_${platform}.mcpb.sha256"
echo " Fetching ${platform}..."
if ! curl -sfL "${url}" -o "bin/kubeshark-mcp_${platform}.mcpb.sha256"; then
echo "::warning::Failed to download SHA256 for ${platform}"
fi
done
echo "Downloaded checksums:"
ls -la bin/*.sha256 2>/dev/null || echo "No SHA256 files found"
- name: Generate server.json
shell: bash
run: |
VERSION="${{ steps.version.outputs.tag }}"
CLEAN_VERSION="${VERSION#v}"
# Read SHA256 hashes
get_sha256() {
local file="bin/kubeshark-mcp_$1.mcpb.sha256"
if [ -f "$file" ]; then
awk '{print $1}' "$file"
else
echo "HASH_NOT_FOUND"
fi
}
DARWIN_ARM64_SHA256=$(get_sha256 "darwin_arm64")
DARWIN_AMD64_SHA256=$(get_sha256 "darwin_amd64")
LINUX_ARM64_SHA256=$(get_sha256 "linux_arm64")
LINUX_AMD64_SHA256=$(get_sha256 "linux_amd64")
WINDOWS_AMD64_SHA256=$(get_sha256 "windows_amd64")
echo "SHA256 hashes:"
echo " darwin_arm64: ${DARWIN_ARM64_SHA256}"
echo " darwin_amd64: ${DARWIN_AMD64_SHA256}"
echo " linux_arm64: ${LINUX_ARM64_SHA256}"
echo " linux_amd64: ${LINUX_AMD64_SHA256}"
echo " windows_amd64: ${WINDOWS_AMD64_SHA256}"
# Generate server.json using jq for proper formatting
jq -n \
--arg version "$CLEAN_VERSION" \
--arg full_version "$VERSION" \
--arg darwin_arm64_sha "$DARWIN_ARM64_SHA256" \
--arg darwin_amd64_sha "$DARWIN_AMD64_SHA256" \
--arg linux_arm64_sha "$LINUX_ARM64_SHA256" \
--arg linux_amd64_sha "$LINUX_AMD64_SHA256" \
--arg windows_amd64_sha "$WINDOWS_AMD64_SHA256" \
'{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.kubeshark/mcp",
"displayName": "Kubeshark",
"description": "Real-time Kubernetes network traffic visibility and API analysis for HTTP, gRPC, Redis, Kafka, DNS.",
"icon": "https://raw.githubusercontent.com/kubeshark/assets/refs/heads/master/logo/ico/icon.ico",
"repository": { "url": "https://github.com/kubeshark/kubeshark", "source": "github" },
"homepage": "https://kubeshark.com",
"license": "Apache-2.0",
"version": $version,
"authors": [{ "name": "Kubeshark", "url": "https://kubeshark.com" }],
"categories": ["kubernetes", "networking", "observability", "debugging", "security"],
"tags": ["kubernetes", "network", "traffic", "api", "http", "grpc", "kafka", "redis", "dns", "pcap", "wireshark", "tcpdump", "observability", "debugging", "microservices"],
"packages": [
{ "registryType": "mcpb", "identifier": ("https://github.com/kubeshark/kubeshark/releases/download/" + $full_version + "/kubeshark-mcp_darwin_arm64.mcpb"), "fileSha256": $darwin_arm64_sha, "transport": { "type": "stdio" } },
{ "registryType": "mcpb", "identifier": ("https://github.com/kubeshark/kubeshark/releases/download/" + $full_version + "/kubeshark-mcp_darwin_amd64.mcpb"), "fileSha256": $darwin_amd64_sha, "transport": { "type": "stdio" } },
{ "registryType": "mcpb", "identifier": ("https://github.com/kubeshark/kubeshark/releases/download/" + $full_version + "/kubeshark-mcp_linux_arm64.mcpb"), "fileSha256": $linux_arm64_sha, "transport": { "type": "stdio" } },
{ "registryType": "mcpb", "identifier": ("https://github.com/kubeshark/kubeshark/releases/download/" + $full_version + "/kubeshark-mcp_linux_amd64.mcpb"), "fileSha256": $linux_amd64_sha, "transport": { "type": "stdio" } },
{ "registryType": "mcpb", "identifier": ("https://github.com/kubeshark/kubeshark/releases/download/" + $full_version + "/kubeshark-mcp_windows_amd64.mcpb"), "fileSha256": $windows_amd64_sha, "transport": { "type": "stdio" } }
],
"tools": [
{ "name": "check_kubeshark_status", "description": "Check if Kubeshark is currently running in the cluster.", "mode": "proxy" },
{ "name": "start_kubeshark", "description": "Deploy Kubeshark to the Kubernetes cluster. Requires --allow-destructive flag.", "mode": "proxy", "destructive": true },
{ "name": "stop_kubeshark", "description": "Remove Kubeshark from the Kubernetes cluster. Requires --allow-destructive flag.", "mode": "proxy", "destructive": true },
{ "name": "list_workloads", "description": "List pods, services, namespaces, and nodes with observed L7 traffic.", "mode": "all" },
{ "name": "list_api_calls", "description": "Query L7 API transactions (HTTP, gRPC, Redis, Kafka, DNS) with KFL filtering.", "mode": "all" },
{ "name": "get_api_call", "description": "Get detailed information about a specific API call including headers and body.", "mode": "all" },
{ "name": "get_api_stats", "description": "Get aggregated API statistics and metrics.", "mode": "all" },
{ "name": "list_l4_flows", "description": "List L4 (TCP/UDP) network flows with traffic statistics.", "mode": "all" },
{ "name": "get_l4_flow_summary", "description": "Get L4 connectivity summary including top talkers and cross-namespace traffic.", "mode": "all" },
{ "name": "list_snapshots", "description": "List all PCAP snapshots.", "mode": "all" },
{ "name": "create_snapshot", "description": "Create a new PCAP snapshot of captured traffic.", "mode": "all" },
{ "name": "get_dissection_status", "description": "Check L7 protocol parsing status.", "mode": "all" },
{ "name": "enable_dissection", "description": "Enable L7 protocol dissection.", "mode": "all" },
{ "name": "disable_dissection", "description": "Disable L7 protocol dissection.", "mode": "all" }
],
"prompts": [
{ "name": "analyze_traffic", "description": "Analyze API traffic patterns and identify issues" },
{ "name": "find_errors", "description": "Find and summarize API errors and failures" },
{ "name": "trace_request", "description": "Trace a request path through microservices" },
{ "name": "show_topology", "description": "Show service communication topology" },
{ "name": "latency_analysis", "description": "Analyze latency patterns and identify slow endpoints" },
{ "name": "security_audit", "description": "Audit traffic for security concerns" },
{ "name": "compare_traffic", "description": "Compare traffic patterns between time periods" },
{ "name": "debug_connection", "description": "Debug connectivity issues between services" }
],
"configuration": {
"properties": {
"url": { "type": "string", "description": "Direct URL to Kubeshark Hub (e.g., https://kubeshark.example.com). When set, connects directly without kubectl/proxy.", "examples": ["https://kubeshark.example.com", "http://localhost:8899"] },
"kubeconfig": { "type": "string", "description": "Path to kubeconfig file for proxy mode.", "examples": ["~/.kube/config", "/path/to/.kube/config"] },
"allow-destructive": { "type": "boolean", "description": "Enable destructive operations (start_kubeshark, stop_kubeshark). Default: false for safety.", "default": false }
}
},
"modes": {
"url": { "description": "Connect directly to an existing Kubeshark deployment via URL. Cluster management tools are disabled.", "args": ["mcp", "--url", "${url}"] },
"proxy": { "description": "Connect via kubectl port-forward. Requires kubeconfig access to the cluster.", "args": ["mcp", "--kubeconfig", "${kubeconfig}"] },
"proxy-destructive": { "description": "Proxy mode with destructive operations enabled.", "args": ["mcp", "--kubeconfig", "${kubeconfig}", "--allow-destructive"] }
}
}' > mcp/server.json
echo ""
echo "Generated server.json:"
cat mcp/server.json
- name: Install mcp-publisher
shell: bash
run: |
echo "Installing mcp-publisher..."
curl -sfL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz
chmod +x mcp-publisher
sudo mv mcp-publisher /usr/local/bin/
echo "mcp-publisher installed successfully"
- name: Login to MCP Registry
if: github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true'
shell: bash
run: mcp-publisher login github-oidc
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to MCP Registry
if: github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true'
shell: bash
run: |
cd mcp
echo "Publishing to MCP Registry..."
if ! mcp-publisher publish; then
echo "::error::Failed to publish to MCP Registry"
exit 1
fi
echo "Successfully published to MCP Registry"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Dry-run summary
if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true'
shell: bash
run: |
echo "=============================================="
echo "DRY RUN - Would publish the following server.json"
echo "=============================================="
cat mcp/server.json
echo ""
echo "=============================================="
echo "SHA256 checksums downloaded:"
echo "=============================================="
cat bin/*.sha256 2>/dev/null || echo "No SHA256 files found"

24
.github/workflows/release-tag.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Auto-tag release
on:
pull_request:
types: [closed]
branches: [master]
jobs:
tag:
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Create and push tag
run: |
VERSION="${GITHUB_HEAD_REF#release/}"
echo "Creating tag $VERSION on master"
git tag "$VERSION"
git push origin "$VERSION"

View File

@@ -43,6 +43,23 @@ jobs:
run: |
echo '${{ steps.version.outputs.tag }}' >> bin/version.txt
- name: Create MCP Registry artifacts
shell: bash
run: |
cd bin
# Create .mcpb copies for MCP Registry (URL must contain "mcp")
for f in kubeshark_linux_amd64 kubeshark_linux_arm64 kubeshark_darwin_amd64 kubeshark_darwin_arm64; do
if [ -f "$f" ]; then
cp "$f" "${f/kubeshark_/kubeshark-mcp_}.mcpb"
shasum -a 256 "${f/kubeshark_/kubeshark-mcp_}.mcpb" > "${f/kubeshark_/kubeshark-mcp_}.mcpb.sha256"
fi
done
# Handle Windows executable
if [ -f "kubeshark.exe" ]; then
cp kubeshark.exe kubeshark-mcp_windows_amd64.mcpb
shasum -a 256 kubeshark-mcp_windows_amd64.mcpb > kubeshark-mcp_windows_amd64.mcpb.sha256
fi
- name: Release
uses: ncipollo/release-action@v1
with:
@@ -52,16 +69,9 @@ jobs:
prerelease: false
bodyFile: 'bin/README.md'
brew:
name: Publish a new Homebrew formulae
mcp-publish:
name: Publish to MCP Registry
needs: [release]
runs-on: ubuntu-latest
steps:
- name: Bump core homebrew formula
uses: mislav/bump-homebrew-formula-action@v3
with:
# A PR will be sent to github.com/Homebrew/homebrew-core to update this formula:
formula-name: kubeshark
push-to: kubeshark/homebrew-core
env:
COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }}
uses: ./.github/workflows/mcp-publish.yml
with:
release_tag: ${{ needs.release.outputs.version }}

View File

@@ -15,7 +15,7 @@ jobs:
timeout-minutes: 20
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
fetch-depth: 2
@@ -29,3 +29,52 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
helm-tests:
name: Helm Chart Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out code
uses: actions/checkout@v5
- name: Set up Helm
uses: azure/setup-helm@v4
- name: Helm lint (default values)
run: helm lint ./helm-chart
- name: Helm lint (S3 values)
run: helm lint ./helm-chart -f ./helm-chart/tests/fixtures/values-s3.yaml
- name: Helm lint (Azure Blob values)
run: helm lint ./helm-chart -f ./helm-chart/tests/fixtures/values-azblob.yaml
- name: Helm lint (GCS values)
run: helm lint ./helm-chart -f ./helm-chart/tests/fixtures/values-gcs.yaml
- name: Helm lint (cloud refs values)
run: helm lint ./helm-chart -f ./helm-chart/tests/fixtures/values-cloud-refs.yaml
- name: Install helm-unittest plugin
run: helm plugin install https://github.com/helm-unittest/helm-unittest --verify=false
- name: Run helm unit tests
run: helm unittest ./helm-chart
- name: Install kubeconform
run: |
curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
sudo mv kubeconform /usr/local/bin/
- name: Validate default template
run: helm template kubeshark ./helm-chart | kubeconform -strict -kubernetes-version 1.35.0 -summary
- name: Validate S3 template
run: helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-s3.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary
- name: Validate Azure Blob template
run: helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-azblob.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary
- name: Validate GCS template
run: helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-gcs.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary

6
.gitignore vendored
View File

@@ -63,4 +63,8 @@ bin
scripts/
# CWD config YAML
kubeshark.yaml
kubeshark.yaml
# Claude Code
CLAUDE.md
.claude/

8
.mcp.json Normal file
View File

@@ -0,0 +1,8 @@
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp"]
}
}
}

View File

@@ -7,7 +7,7 @@ Please read and follow the guidelines below.
## Communication
* Before starting work on a major feature, please reach out to us via [GitHub](https://github.com/kubeshark/kubeshark), [Discord](https://discord.gg/WkvRGMUcx7), [Slack](https://join.slack.com/t/kubeshark/shared_invite/zt-1k3sybpq9-uAhFkuPJiJftKniqrGHGhg), [email](mailto:info@kubeshark.co), etc. We will make sure no one else is already working on it. A _major feature_ is defined as any change that is > 100 LOC altered (not including tests), or changes any user-facing behavior
* Before starting work on a major feature, please reach out to us via [GitHub](https://github.com/kubeshark/kubeshark), [Discord](https://discord.gg/WkvRGMUcx7), [Slack](https://join.slack.com/t/kubeshark/shared_invite/zt-1k3sybpq9-uAhFkuPJiJftKniqrGHGhg), [email](mailto:info@kubeshark.com), etc. We will make sure no one else is already working on it. A _major feature_ is defined as any change that is > 100 LOC altered (not including tests), or changes any user-facing behavior
* Small patches and bug fixes don't need prior communication.
## Contribution Requirements

234
Makefile
View File

@@ -74,6 +74,79 @@ clean: ## Clean all build artifacts.
test: ## Run cli tests.
@go test ./... -coverpkg=./... -race -coverprofile=coverage.out -covermode=atomic
test-integration: ## Run integration tests (requires Kubernetes cluster).
@echo "Running integration tests..."
@LOG_FILE=$$(mktemp /tmp/integration-test.XXXXXX.log); \
go test -tags=integration -timeout $${INTEGRATION_TIMEOUT:-5m} -v ./integration/... 2>&1 | tee $$LOG_FILE; \
status=$$?; \
echo ""; \
echo "========================================"; \
echo " INTEGRATION TEST SUMMARY"; \
echo "========================================"; \
grep -E "^(--- PASS|--- FAIL|--- SKIP)" $$LOG_FILE || true; \
echo "----------------------------------------"; \
pass=$$(grep -c "^--- PASS" $$LOG_FILE 2>/dev/null || true); \
fail=$$(grep -c "^--- FAIL" $$LOG_FILE 2>/dev/null || true); \
skip=$$(grep -c "^--- SKIP" $$LOG_FILE 2>/dev/null || true); \
echo "PASSED: $${pass:-0}"; \
echo "FAILED: $${fail:-0}"; \
echo "SKIPPED: $${skip:-0}"; \
echo "========================================"; \
rm -f $$LOG_FILE; \
exit $$status
test-integration-mcp: ## Run only MCP integration tests.
@echo "Running MCP integration tests..."
@LOG_FILE=$$(mktemp /tmp/integration-test.XXXXXX.log); \
go test -tags=integration -timeout $${INTEGRATION_TIMEOUT:-5m} -v ./integration/ -run "MCP" 2>&1 | tee $$LOG_FILE; \
status=$$?; \
echo ""; \
echo "========================================"; \
echo " INTEGRATION TEST SUMMARY"; \
echo "========================================"; \
grep -E "^(--- PASS|--- FAIL|--- SKIP)" $$LOG_FILE || true; \
echo "----------------------------------------"; \
pass=$$(grep -c "^--- PASS" $$LOG_FILE 2>/dev/null || true); \
fail=$$(grep -c "^--- FAIL" $$LOG_FILE 2>/dev/null || true); \
skip=$$(grep -c "^--- SKIP" $$LOG_FILE 2>/dev/null || true); \
echo "PASSED: $${pass:-0}"; \
echo "FAILED: $${fail:-0}"; \
echo "SKIPPED: $${skip:-0}"; \
echo "========================================"; \
rm -f $$LOG_FILE; \
exit $$status
test-integration-short: ## Run quick integration tests (skips long-running tests).
@echo "Running quick integration tests..."
@LOG_FILE=$$(mktemp /tmp/integration-test.XXXXXX.log); \
go test -tags=integration -timeout $${INTEGRATION_TIMEOUT:-2m} -short -v ./integration/... 2>&1 | tee $$LOG_FILE; \
status=$$?; \
echo ""; \
echo "========================================"; \
echo " INTEGRATION TEST SUMMARY"; \
echo "========================================"; \
grep -E "^(--- PASS|--- FAIL|--- SKIP)" $$LOG_FILE || true; \
echo "----------------------------------------"; \
pass=$$(grep -c "^--- PASS" $$LOG_FILE 2>/dev/null || true); \
fail=$$(grep -c "^--- FAIL" $$LOG_FILE 2>/dev/null || true); \
skip=$$(grep -c "^--- SKIP" $$LOG_FILE 2>/dev/null || true); \
echo "PASSED: $${pass:-0}"; \
echo "FAILED: $${fail:-0}"; \
echo "SKIPPED: $${skip:-0}"; \
echo "========================================"; \
rm -f $$LOG_FILE; \
exit $$status
helm-test: ## Run Helm lint and unit tests.
helm lint ./helm-chart
helm unittest ./helm-chart
helm-test-full: helm-test ## Run Helm tests with kubeconform schema validation.
helm template kubeshark ./helm-chart | kubeconform -strict -kubernetes-version 1.35.0 -summary
helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-s3.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary
helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-azblob.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary
helm template kubeshark ./helm-chart -f ./helm-chart/tests/fixtures/values-gcs.yaml | kubeconform -strict -kubernetes-version 1.35.0 -summary
lint: ## Lint the source code.
golangci-lint run
@@ -84,8 +157,10 @@ kubectl-view-kubeshark-resources: ## This command outputs all Kubernetes resourc
./kubectl.sh view-kubeshark-resources
generate-helm-values: ## Generate the Helm values from config.yaml
mv ~/.kubeshark/config.yaml ~/.kubeshark/config.yaml.old; bin/kubeshark__ config>helm-chart/values.yaml;mv ~/.kubeshark/config.yaml.old ~/.kubeshark/config.yaml
sed -i 's/^license:.*/license: ""/' helm-chart/values.yaml && sed -i '1i # find a detailed description here: https://github.com/kubeshark/kubeshark/blob/master/helm-chart/README.md' helm-chart/values.yaml
# [ -f ~/.kubeshark/config.yaml ] && mv ~/.kubeshark/config.yaml ~/.kubeshark/config.yaml.old
bin/kubeshark__ config>helm-chart/values.yaml
# [ -f ~/.kubeshark/config.yaml.old ] && mv ~/.kubeshark/config.yaml.old ~/.kubeshark/config.yaml
# sed -i 's/^license:.*/license: ""/' helm-chart/values.yaml && sed -i '1i # find a detailed description here: https://github.com/kubeshark/kubeshark/blob/master/helm-chart/README.md' helm-chart/values.yaml
generate-manifests: ## Generate the manifests from the Helm chart using default configuration
helm template kubeshark -n default ./helm-chart > ./manifests/complete.yaml
@@ -177,29 +252,140 @@ proxy:
port-forward:
kubectl port-forward $$(kubectl get pods | awk '$$1 ~ /^$(POD_PREFIX)/' | awk 'END {print $$1}') $(SRC_PORT):$(DST_PORT)
release:
@cd ../worker && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../tracer && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../hub && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../front && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../kubeshark && git checkout master && git pull && sed -i 's/^version:.*/version: "$(VERSION)"/' helm-chart/Chart.yaml && make && make generate-helm-values && make generate-manifests
@git add -A . && git commit -m ":bookmark: Bump the Helm chart version to $(VERSION)" && git push
@git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd helm-chart && cp -r . ../../kubeshark.github.io/charts/chart
@cd ../../kubeshark.github.io/ && git add -A . && git commit -m ":sparkles: Update the Helm chart" && git push
@cd ../kubeshark
release: ## Print release workflow instructions.
@echo "Release workflow — each step is idempotent and can be rerun on its own:"
@echo ""
@echo " 1. make release-siblings VERSION=x.y.z"
@echo " Tag worker, hub, front with vx.y.z. Also run standalone when"
@echo " rebuilding docker images without cutting a full release."
@echo ""
@echo " 2. make release-pr-kubeshark VERSION=x.y.z"
@echo " Bump Helm Chart.yaml, build, open release PR on kubeshark."
@echo ""
@echo " 3. make release-pr-helm VERSION=x.y.z"
@echo " Sync helm-chart/ into kubeshark.github.io, open helm PR."
@echo " Requires release/vx.y.z branch (created by step 2)."
@echo ""
@echo " Shortcut: make release-pr VERSION=x.y.z runs 1 → 2 → 3."
@echo ""
@echo " After both PRs merge: tag is created automatically,"
@echo " or run: make release-tag VERSION=x.y.z"
soft-release:
@cd ../worker && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../tracer && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../hub && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../front && git checkout master && git pull && git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
@cd ../kubeshark && git checkout master && git pull && sed -i 's/^version:.*/version: "$(VERSION)"/' helm-chart/Chart.yaml && make && make generate-helm-values && make generate-manifests
@git add -A . && git commit -m ":bookmark: Bump the Helm chart version to $(VERSION)" && git push
# @git tag -d v$(VERSION); git tag v$(VERSION) && git push origin --tags
# @cd helm-chart && cp -r . ../../kubeshark.github.io/charts/chart
# @cd ../../kubeshark.github.io/ && git add -A . && git commit -m ":sparkles: Update the Helm chart" && git push
# @cd ../kubeshark
# Internal: validate VERSION before any release-* target runs.
_release-check-version:
@if [ -z "$(VERSION)" ]; then echo "ERROR: VERSION is required. Usage: make <target> VERSION=x.y.z"; exit 1; fi
@echo "$(VERSION)" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+' || { echo "ERROR: VERSION must be semver (e.g. 53.2.4)"; exit 1; }
release-siblings: _release-check-version ## Tag worker, hub, front with v$(VERSION). Idempotent; standalone for docker-image-only updates.
@for repo in worker hub front; do \
echo "==> $$repo: ensuring v$(VERSION) tag"; \
(cd ../$$repo && git checkout master && git pull) || exit 1; \
if (cd ../$$repo && git ls-remote --tags origin "refs/tags/v$(VERSION)" | grep -q .); then \
echo " v$(VERSION) already on origin — skipping"; \
else \
(cd ../$$repo && git tag -d v$(VERSION) 2>/dev/null; git tag v$(VERSION) && git push origin "refs/tags/v$(VERSION)") || exit 1; \
fi; \
done
release-pr-kubeshark: _release-check-version ## Bump Chart.yaml, build, open release PR on kubeshark.
@cd ../kubeshark && git checkout master && git pull
@NEW=$$(echo $(VERSION) | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/'); \
CUR=$$(awk '/^version:/ {gsub(/"/,"",$$2); print $$2; exit}' helm-chart/Chart.yaml); \
if [ "$$CUR" != "$$NEW" ]; then \
sed -i '' "s/^version:.*/version: \"$$NEW\"/" helm-chart/Chart.yaml; \
else \
echo "Chart.yaml already at $$NEW"; \
fi
@$(MAKE) build VER=$(VERSION)
@if [ "$(shell uname)" = "Darwin" ]; then \
codesign --sign - --force --preserve-metadata=entitlements,requirements,flags,runtime ./bin/kubeshark__; \
fi
@$(MAKE) generate-helm-values && $(MAKE) generate-manifests
@if git show-ref --verify --quiet refs/heads/release/v$(VERSION); then \
git branch -D release/v$(VERSION); \
fi
@git checkout -b release/v$(VERSION)
@git add -A .
@if ! git diff --cached --quiet; then \
git commit -m ":bookmark: Bump the Helm chart version to $(VERSION)"; \
else \
echo "nothing to commit"; \
fi
@git push --force-with-lease -u origin release/v$(VERSION)
@if gh pr view release/v$(VERSION) --json number >/dev/null 2>&1; then \
echo "PR already exists for release/v$(VERSION)"; \
else \
gh pr create --title ":bookmark: Release v$(VERSION)" \
--body "Automated release PR for v$(VERSION)." \
--base master \
--reviewer corest; \
fi
release-pr-helm: _release-check-version ## Sync helm-chart/ to kubeshark.github.io and open the helm PR. Requires release/v$(VERSION) branch (step 2).
@git fetch origin "refs/heads/release/v$(VERSION):refs/heads/release/v$(VERSION)" 2>/dev/null || true
@if ! git show-ref --verify --quiet refs/heads/release/v$(VERSION); then \
echo "ERROR: release/v$(VERSION) branch not found locally or on origin."; \
echo "Run 'make release-pr-kubeshark VERSION=$(VERSION)' first."; \
exit 1; \
fi
@git checkout release/v$(VERSION)
@cd ../kubeshark.github.io && git checkout master && git pull \
&& rm -rf charts/chart && mkdir -p charts/chart \
&& cp -r ../kubeshark/helm-chart/ charts/chart/
@cd ../kubeshark.github.io && \
if git show-ref --verify --quiet refs/heads/helm-v$(VERSION); then \
git branch -D helm-v$(VERSION); \
fi && \
git checkout -b helm-v$(VERSION) && \
git add -A . && \
if ! git diff --cached --quiet; then \
git commit -m ":sparkles: Update the Helm chart to v$(VERSION)"; \
else \
echo "nothing to commit"; \
fi && \
git push --force-with-lease -u origin helm-v$(VERSION) && \
if ! gh pr view helm-v$(VERSION) --json number >/dev/null 2>&1; then \
gh pr create --title ":sparkles: Helm chart v$(VERSION)" \
--body "Update Helm chart for release v$(VERSION)." \
--base master \
--reviewer corest; \
else \
echo "PR already exists for helm-v$(VERSION)"; \
fi && \
git checkout master
@cd ../kubeshark && git checkout master && git pull
release-pr: release-siblings release-pr-kubeshark release-pr-helm ## Run release-siblings, release-pr-kubeshark, and release-pr-helm in sequence.
@echo ""
@echo "Release PRs created (or already present):"
@echo " - kubeshark: Review and merge the release PR."
@echo " - kubeshark.github.io: Review and merge the helm chart PR."
@echo "Tag will be created automatically, or run: make release-tag VERSION=$(VERSION)"
release-tag: ## Step 2 (fallback): Tag master after release PR is merged.
@echo "Verifying release PR was merged..."
@if ! gh pr list --state merged --head release/v$(VERSION) --json number --jq '.[0].number' | grep -q .; then \
echo "Error: No merged PR found for release/v$(VERSION). Merge the PR first."; \
exit 1; \
fi
@git checkout master && git pull
@git tag -d v$(VERSION) 2>/dev/null; git tag v$(VERSION) && git push origin --tags
@echo ""
@echo "Tagged v$(VERSION) on master. GitHub Actions will build the release."
release-dry-run:
@cd ../worker && git checkout master && git pull
# @cd ../tracer && git checkout master && git pull
@cd ../hub && git checkout master && git pull
@cd ../front && git checkout master && git pull
@cd ../kubeshark && sed -i "s/^version:.*/version: \"$(shell echo $(VERSION) | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+)\..*/\1/')\"/" helm-chart/Chart.yaml && make
# @if [ "$(shell uname)" = "Darwin" ]; then \
# codesign --sign - --force --preserve-metadata=entitlements,requirements,flags,runtime ./bin/kubeshark__; \
# fi
@make generate-helm-values && make generate-manifests
@rm -rf ../kubeshark.github.io/charts/chart && mkdir ../kubeshark.github.io/charts/chart && cp -r helm-chart/ ../kubeshark.github.io/charts/chart/
@cd ../kubeshark.github.io/
@cd ../kubeshark
branch:
@cd ../worker && git checkout master && git pull && git checkout -b $(name); git push --set-upstream origin $(name)

164
README.md
View File

@@ -1,81 +1,151 @@
<p align="center">
<img src="https://raw.githubusercontent.com/kubeshark/assets/master/svg/kubeshark-logo.svg" alt="Kubeshark: Traffic analyzer for Kubernetes." height="128px"/>
<img src="https://raw.githubusercontent.com/kubeshark/assets/master/svg/kubeshark-logo.svg" alt="Kubeshark" height="120px"/>
</p>
<p align="center">
<a href="https://github.com/kubeshark/kubeshark/releases/latest">
<img alt="GitHub Latest Release" src="https://img.shields.io/github/v/release/kubeshark/kubeshark?logo=GitHub&style=flat-square">
</a>
<a href="https://hub.docker.com/r/kubeshark/worker">
<img alt="Docker pulls" src="https://img.shields.io/docker/pulls/kubeshark/worker?color=%23099cec&logo=Docker&style=flat-square">
</a>
<a href="https://hub.docker.com/r/kubeshark/worker">
<img alt="Image size" src="https://img.shields.io/docker/image-size/kubeshark/kubeshark/latest?logo=Docker&style=flat-square">
</a>
<a href="https://discord.gg/WkvRGMUcx7">
<img alt="Discord" src="https://img.shields.io/discord/1042559155224973352?logo=Discord&style=flat-square&label=discord">
</a>
<a href="https://join.slack.com/t/kubeshark/shared_invite/zt-1m90td3n7-VHxN_~V5kVp80SfQW3SfpA">
<img alt="Slack" src="https://img.shields.io/badge/slack-join_chat-green?logo=Slack&style=flat-square&label=slack">
</a>
<a href="https://github.com/kubeshark/kubeshark/releases/latest"><img alt="Release" src="https://img.shields.io/github/v/release/kubeshark/kubeshark?logo=GitHub&style=flat-square"></a>
<a href="https://hub.docker.com/r/kubeshark/worker"><img alt="Docker pulls" src="https://img.shields.io/docker/pulls/kubeshark/worker?color=%23099cec&logo=Docker&style=flat-square"></a>
<a href="https://discord.gg/WkvRGMUcx7"><img alt="Discord" src="https://img.shields.io/discord/1042559155224973352?logo=Discord&style=flat-square&label=discord"></a>
<a href="https://join.slack.com/t/kubeshark/shared_invite/zt-3jdcdgxdv-1qNkhBh9c6CFoE7bSPkpBQ"><img alt="Slack" src="https://img.shields.io/badge/slack-join_chat-green?logo=Slack&style=flat-square"></a>
</p>
<p align="center"><b>Network Observability for SREs & AI Agents</b></p>
<p align="center">
<b>
Want to see Kubeshark in action, right now? Visit this
<a href="https://demo.kubeshark.co/">live demo deployment</a> of Kubeshark.
</b>
<a href="https://demo.kubeshark.com/">Live Demo</a> · <a href="https://docs.kubeshark.com">Docs</a>
</p>
**Kubeshark** is an API Traffic Analyzer for [**Kubernetes**](https://kubernetes.io/) providing real-time, protocol-level visibility into Kubernetes internal network, capturing and monitoring all traffic and payloads going in, out and across containers, pods, nodes and clusters.
---
![Simple UI](https://github.com/kubeshark/assets/raw/master/png/kubeshark-ui.png)
Kubeshark indexes cluster-wide network traffic at the kernel level using eBPF — delivering instant answers to any query using network, API, and Kubernetes semantics.
Think [TCPDump](https://en.wikipedia.org/wiki/Tcpdump) and [Wireshark](https://www.wireshark.org/) re-invented for Kubernetes
**What you can do:**
## Getting Started
- **Download Retrospective PCAPs** — cluster-wide packet captures filtered by nodes, time, workloads, and IPs. Store PCAPs for long-term retention and later investigation.
- **Visualize Network Data** — explore traffic matching queries with API, Kubernetes, or network semantics through a real-time dashboard.
- **See Encrypted Traffic in Plain Text** — automatically decrypt TLS/mTLS traffic using eBPF, with no key management or sidecars required.
- **Integrate with AI** — connect your favorite AI assistant (e.g. Claude, Copilot) to include network data in AI-driven workflows like incident response and root cause analysis.
Download **Kubeshark**'s binary distribution [latest release](https://github.com/kubeshark/kubeshark/releases/latest) and run following one of these examples:
![Kubeshark](https://github.com/kubeshark/assets/raw/master/png/stream.png)
```shell
kubeshark tap
---
## Get Started
```bash
helm repo add kubeshark https://helm.kubeshark.com
helm install kubeshark kubeshark/kubeshark
kubectl port-forward svc/kubeshark-front 8899:80
```
```shell
kubeshark tap -n sock-shop "(catalo*|front-end*)"
```
Open `http://localhost:8899` in your browser. You're capturing traffic.
Running any of the :point_up: above commands will open the [Web UI](https://docs.kubeshark.co/en/ui) in your browser which streams the traffic in your Kubernetes cluster in real-time.
> For production use, we recommend using an [ingress controller](https://docs.kubeshark.com/en/ingress) instead of port-forward.
### Homebrew
**Connect an AI agent** via MCP:
[Homebrew](https://brew.sh/) :beer: users install Kubeshark CLI with:
```shell
```bash
brew install kubeshark
claude mcp add kubeshark -- kubeshark mcp
```
### Helm
[MCP setup guide →](https://docs.kubeshark.com/en/mcp)
Add the helm repository and install the chart:
---
```shell
helm repo add kubeshark https://helm.kubeshark.co
helm install kubeshark kubeshark/kubeshark
### Network Data for AI Agents
Kubeshark exposes cluster-wide network data via [MCP](https://docs.kubeshark.com/en/mcp) — enabling AI agents to query traffic, investigate API calls, and perform root cause analysis through natural language.
> *"Why did checkout fail at 2:15 PM?"*
> *"Which services have error rates above 1%?"*
> *"Show TCP retransmission rates across all node-to-node paths"*
> *"Trace request abc123 through all services"*
Works with Claude Code, Cursor, and any MCP-compatible AI.
![MCP Demo](https://github.com/kubeshark/assets/raw/master/gif/mcp-demo.gif)
[MCP setup guide →](https://docs.kubeshark.com/en/mcp)
### AI Skills
Open-source, reusable skills that teach AI agents domain-specific workflows on top of Kubeshark's MCP tools:
| Skill | Description |
|-------|-------------|
| **[Network RCA](skills/network-rca/)** | Retrospective root cause analysis — snapshots, dissection, PCAP extraction, trend comparison |
| **[KFL](skills/kfl/)** | KFL (Kubeshark Filter Language) expert — writes, debugs, and optimizes traffic filters |
Install as a Claude Code plugin:
```
/plugin marketplace add kubeshark/kubeshark
/plugin install kubeshark
```
## Building From Source
Or clone and use directly — skills trigger automatically based on conversation context.
Clone this repository and run `make` command to build it. After the build is complete, the executable can be found at `./bin/kubeshark__`.
[AI Skills docs →](https://docs.kubeshark.com/en/mcp/skills)
## Documentation
---
To learn more, read the [documentation](https://docs.kubeshark.co).
### Query with API, Kubernetes, and Network Semantics
Kubeshark indexes cluster-wide network traffic by parsing it according to protocol specifications, with support for HTTP, gRPC, Redis, Kafka, DNS, and more. A single [KFL query](https://docs.kubeshark.com/en/v2/kfl2) can combine all three semantic layers — Kubernetes identity, API context, and network attributes — to pinpoint exactly the traffic you need. No code instrumentation required.
![KFL query combining API, Kubernetes, and network semantics](https://github.com/kubeshark/assets/raw/master/png/kfl-semantics.png)
[KFL reference →](https://docs.kubeshark.com/en/v2/kfl2) · [Traffic indexing →](https://docs.kubeshark.com/en/v2/l7_api_dissection)
### Workload Dependency Map
A visual map of how workloads communicate, showing dependencies, traffic volume, and protocol usage across the cluster.
![Service Map](https://github.com/kubeshark/assets/raw/master/png/servicemap.png)
[Learn more →](https://docs.kubeshark.com/en/v2/service_map)
### Traffic Retention & PCAP Export
Capture and retain raw network traffic cluster-wide, including decrypted TLS. Download PCAPs scoped by time range, nodes, workloads, and IPs — ready for Wireshark or any PCAP-compatible tool. Store snapshots in cloud storage (S3, Azure Blob, GCS) for long-term retention and cross-cluster sharing.
![Traffic Retention](https://github.com/kubeshark/assets/raw/master/png/snapshots-list.png)
[Snapshots guide →](https://docs.kubeshark.com/en/v2/traffic_snapshots) · [Cloud storage →](https://docs.kubeshark.com/en/snapshots_cloud_storage)
---
## Features
| Feature | Description |
|---------|-------------|
| [**Traffic Snapshots**](https://docs.kubeshark.com/en/v2/traffic_snapshots) | Point-in-time snapshots with cloud storage (S3, Azure Blob, GCS), PCAP export for Wireshark |
| [**Traffic Indexing**](https://docs.kubeshark.com/en/v2/l7_api_dissection) | Real-time and delayed L7 indexing with request/response matching and full payloads |
| [**Protocol Support**](https://docs.kubeshark.com/en/protocols) | HTTP, gRPC, GraphQL, Redis, Kafka, DNS, and more |
| [**TLS Decryption**](https://docs.kubeshark.com/en/encrypted_traffic) | eBPF-based decryption without key management, included in snapshots |
| [**AI Integration**](https://docs.kubeshark.com/en/mcp) | MCP server + open-source AI skills for network RCA and traffic filtering |
| [**KFL Query Language**](https://docs.kubeshark.com/en/v2/kfl2) | CEL-based query language with Kubernetes, API, and network semantics |
| [**100% On-Premises**](https://docs.kubeshark.com/en/air_gapped) | Air-gapped support, no external dependencies |
---
## Install
| Method | Command |
|--------|---------|
| Helm | `helm repo add kubeshark https://helm.kubeshark.com && helm install kubeshark kubeshark/kubeshark` |
| Homebrew | `brew install kubeshark && kubeshark tap` |
| Binary | [Download](https://github.com/kubeshark/kubeshark/releases/latest) |
[Installation guide →](https://docs.kubeshark.com/en/install)
---
## Contributing
We :heart: pull requests! See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guide.
We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md).
## Code of Conduct
## License
This project is for everyone. We ask that our users and contributors take a few minutes to review our [Code of Conduct](CODE_OF_CONDUCT.md).
[Apache-2.0](LICENSE)

View File

@@ -10,7 +10,7 @@ curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/download/_VER
**Mac** (AArch64/Apple M1 silicon)
```
rm -f kubeshark && curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/download/_VER_/kubeshark_darwin_arm64 && chmod 755 kubeshark
curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/download/_VER_/kubeshark_darwin_arm64 && chmod 755 kubeshark
```
**Linux** (x86-64)

View File

@@ -2,7 +2,6 @@ package cmd
import (
"fmt"
"path"
"github.com/creasty/defaults"
"github.com/kubeshark/kubeshark/config"
@@ -52,5 +51,5 @@ func init() {
log.Debug().Err(err).Send()
}
configCmd.Flags().BoolP(configStructs.RegenerateConfigName, "r", defaultConfig.Config.Regenerate, fmt.Sprintf("Regenerate the config file with default values to path %s", path.Join(misc.GetDotFolderPath(), "config.yaml")))
configCmd.Flags().BoolP(configStructs.RegenerateConfigName, "r", defaultConfig.Config.Regenerate, fmt.Sprintf("Regenerate the config file with default values to path %s", config.GetConfigFilePath(nil)))
}

125
cmd/mcp.go Normal file
View File

@@ -0,0 +1,125 @@
package cmd
import (
"github.com/kubeshark/kubeshark/config"
"github.com/spf13/cobra"
)
var mcpURL string
var mcpKubeconfig string
var mcpListTools bool
var mcpConfig bool
var mcpAllowDestructive bool
var mcpCmd = &cobra.Command{
Use: "mcp",
Short: "Run MCP (Model Context Protocol) server for AI assistant integration",
Long: `Run an MCP server over stdio that exposes Kubeshark's L7 API visibility
to AI assistants like Claude Desktop.
TOOLS PROVIDED:
Cluster Management (work without Kubeshark running):
- check_kubeshark_status: Check if Kubeshark is running in the cluster
- start_kubeshark: Start Kubeshark to capture traffic
- stop_kubeshark: Stop Kubeshark and clean up resources
Traffic Analysis (require Kubeshark running):
- list_workloads: Discover pods, services, namespaces, and nodes with L7 traffic
- list_api_calls: Query L7 API transactions (HTTP, gRPC, etc.)
- get_api_call: Get detailed information about a specific API call
- get_api_stats: Get aggregated API statistics
CONFIGURATION:
To use with Claude Desktop, add to your claude_desktop_config.json
(typically at ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"kubeshark": {
"command": "/path/to/kubeshark",
"args": ["mcp", "--kubeconfig", "/Users/YOUR_USERNAME/.kube/config"]
}
}
}
DIRECT URL MODE:
If Kubeshark is already running and accessible via URL (e.g., exposed via ingress),
you can connect directly without needing kubectl/kubeconfig:
{
"mcpServers": {
"kubeshark": {
"command": "/path/to/kubeshark",
"args": ["mcp", "--url", "https://kubeshark.example.com"]
}
}
}
In URL mode, destructive tools (start/stop) are disabled since Kubeshark is
managed externally. The check_kubeshark_status tool remains available to confirm connectivity.
DESTRUCTIVE OPERATIONS:
By default, destructive operations (start_kubeshark, stop_kubeshark) are disabled
to prevent accidental cluster modifications. To enable them, use --allow-destructive:
{
"mcpServers": {
"kubeshark": {
"command": "/path/to/kubeshark",
"args": ["mcp", "--allow-destructive", "--kubeconfig", "/path/to/.kube/config"]
}
}
}
CUSTOM SETTINGS:
To use custom settings when starting Kubeshark, use the --set flag:
{
"mcpServers": {
"kubeshark": {
"command": "/path/to/kubeshark",
"args": ["mcp", "--set", "tap.docker.tag=v52.3"],
...
}
}
}
Multiple --set flags can be used for different settings.`,
RunE: func(cmd *cobra.Command, args []string) error {
// Handle --mcp-config flag
if mcpConfig {
printMCPConfig(mcpURL, mcpKubeconfig)
return nil
}
// Set kubeconfig path if provided
if mcpKubeconfig != "" {
config.Config.Kube.ConfigPathStr = mcpKubeconfig
}
// Handle --list-tools flag
if mcpListTools {
listMCPTools(mcpURL)
return nil
}
setFlags, _ := cmd.Flags().GetStringSlice(config.SetCommandName)
runMCPWithConfig(setFlags, mcpURL, mcpAllowDestructive)
return nil
},
}
func init() {
rootCmd.AddCommand(mcpCmd)
mcpCmd.Flags().StringVar(&mcpURL, "url", "", "Direct URL to Kubeshark (e.g., https://kubeshark.example.com). When set, connects directly without kubectl/proxy and disables start/stop tools.")
mcpCmd.Flags().StringVar(&mcpKubeconfig, "kubeconfig", "", "Path to kubeconfig file (e.g., /Users/me/.kube/config)")
mcpCmd.Flags().BoolVar(&mcpListTools, "list-tools", false, "List available MCP tools and exit")
mcpCmd.Flags().BoolVar(&mcpConfig, "mcp-config", false, "Print MCP client configuration JSON and exit")
mcpCmd.Flags().BoolVar(&mcpAllowDestructive, "allow-destructive", false, "Enable destructive operations (start_kubeshark, stop_kubeshark). Without this flag, only read-only traffic analysis tools are available.")
}

1231
cmd/mcpRunner.go Normal file

File diff suppressed because it is too large Load Diff

688
cmd/mcp_test.go Normal file
View File

@@ -0,0 +1,688 @@
package cmd
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func newTestMCPServer() *mcpServer {
return &mcpServer{httpClient: &http.Client{}, stdin: &bytes.Buffer{}, stdout: &bytes.Buffer{}}
}
func sendRequest(s *mcpServer, method string, id any, params any) string {
req := jsonRPCRequest{
JSONRPC: "2.0",
ID: id,
Method: method,
}
if params != nil {
paramsBytes, _ := json.Marshal(params)
req.Params = paramsBytes
}
s.handleRequest(&req)
output := s.stdout.(*bytes.Buffer).String()
s.stdout.(*bytes.Buffer).Reset()
return output
}
func parseResponse(t *testing.T, output string) jsonRPCResponse {
var resp jsonRPCResponse
if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &resp); err != nil {
t.Fatalf("Failed to parse response: %v\nOutput: %s", err, output)
}
return resp
}
func TestMCP_Initialize(t *testing.T) {
s := newTestMCPServer()
resp := parseResponse(t, sendRequest(s, "initialize", 1, nil))
if resp.ID != float64(1) || resp.Error != nil {
t.Fatalf("Expected ID 1 with no error, got ID=%v, error=%v", resp.ID, resp.Error)
}
result := resp.Result.(map[string]any)
if result["protocolVersion"] != "2024-11-05" {
t.Errorf("Expected protocolVersion 2024-11-05, got %v", result["protocolVersion"])
}
if result["serverInfo"].(map[string]any)["name"] != "kubeshark-mcp" {
t.Error("Expected server name kubeshark-mcp")
}
if !strings.Contains(result["instructions"].(string), "check_kubeshark_status") {
t.Error("Instructions should mention check_kubeshark_status")
}
if _, ok := result["capabilities"].(map[string]any)["prompts"]; !ok {
t.Error("Expected prompts capability")
}
}
func TestMCP_Ping(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "ping", 42, nil))
if resp.ID != float64(42) || resp.Error != nil || len(resp.Result.(map[string]any)) != 0 {
t.Errorf("Expected ID 42, no error, empty result")
}
}
func TestMCP_InitializedNotification(t *testing.T) {
s := newTestMCPServer()
for _, method := range []string{"initialized", "notifications/initialized"} {
if output := sendRequest(s, method, nil, nil); output != "" {
t.Errorf("Expected no output for %s, got: %s", method, output)
}
}
}
func TestMCP_UnknownMethod(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "unknown/method", 1, nil))
if resp.Error == nil || resp.Error.Code != -32601 {
t.Fatalf("Expected error code -32601, got %v", resp.Error)
}
}
func TestMCP_PromptsList(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "prompts/list", 1, nil))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
prompts := resp.Result.(map[string]any)["prompts"].([]any)
if len(prompts) != 1 || prompts[0].(map[string]any)["name"] != "kubeshark_usage" {
t.Error("Expected 1 prompt named 'kubeshark_usage'")
}
}
func TestMCP_PromptsGet(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "prompts/get", 1, map[string]any{"name": "kubeshark_usage"}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
messages := resp.Result.(map[string]any)["messages"].([]any)
if len(messages) == 0 {
t.Fatal("Expected at least one message")
}
text := messages[0].(map[string]any)["content"].(map[string]any)["text"].(string)
for _, phrase := range []string{"check_kubeshark_status", "start_kubeshark", "stop_kubeshark"} {
if !strings.Contains(text, phrase) {
t.Errorf("Prompt should contain '%s'", phrase)
}
}
}
func TestMCP_PromptsGet_UnknownPrompt(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "prompts/get", 1, map[string]any{"name": "unknown"}))
if resp.Error == nil || resp.Error.Code != -32602 {
t.Fatalf("Expected error code -32602, got %v", resp.Error)
}
}
func TestMCP_ToolsList_CLIOnly(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "tools/list", 1, nil))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
tools := resp.Result.(map[string]any)["tools"].([]any)
// Should have check_kubeshark_status + get_file_url + download_file = 3 tools
if len(tools) != 3 {
t.Errorf("Expected 3 tools, got %d", len(tools))
}
toolNames := make(map[string]bool)
for _, tool := range tools {
toolNames[tool.(map[string]any)["name"].(string)] = true
}
for _, expected := range []string{"check_kubeshark_status", "get_file_url", "download_file"} {
if !toolNames[expected] {
t.Errorf("Missing expected tool: %s", expected)
}
}
}
func TestMCP_ToolsList_WithDestructive(t *testing.T) {
s := &mcpServer{httpClient: &http.Client{}, stdin: &bytes.Buffer{}, stdout: &bytes.Buffer{}, allowDestructive: true}
resp := parseResponse(t, sendRequest(s, "tools/list", 1, nil))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
tools := resp.Result.(map[string]any)["tools"].([]any)
toolNames := make(map[string]bool)
for _, tool := range tools {
toolNames[tool.(map[string]any)["name"].(string)] = true
}
for _, expected := range []string{"check_kubeshark_status", "start_kubeshark", "stop_kubeshark"} {
if !toolNames[expected] {
t.Errorf("Missing expected tool: %s", expected)
}
}
}
func TestMCP_ToolsList_WithHubBackend(t *testing.T) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" || r.URL.Path == "" {
_, _ = w.Write([]byte(`{"name":"hub","tools":[{"name":"list_workloads","description":"","inputSchema":{}},{"name":"list_api_calls","description":"","inputSchema":{}}]}`))
}
}))
defer mockServer.Close()
s := &mcpServer{httpClient: &http.Client{}, stdin: &bytes.Buffer{}, stdout: &bytes.Buffer{}, hubBaseURL: mockServer.URL, backendInitialized: true, allowDestructive: true}
resp := parseResponse(t, sendRequest(s, "tools/list", 1, nil))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
tools := resp.Result.(map[string]any)["tools"].([]any)
// Should have CLI tools (3) + file tools (2) + Hub tools (2) = 7 tools
if len(tools) < 7 {
t.Errorf("Expected at least 7 tools, got %d", len(tools))
}
}
func TestMCP_ToolsCallUnknownTool(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
})
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "unknown"}))
if !resp.Result.(map[string]any)["isError"].(bool) {
t.Error("Expected isError=true for unknown tool")
}
}
func TestMCP_ToolsCallInvalidParams(t *testing.T) {
s := newTestMCPServer()
req := jsonRPCRequest{JSONRPC: "2.0", ID: 1, Method: "tools/call", Params: json.RawMessage(`"invalid"`)}
s.handleRequest(&req)
resp := parseResponse(t, s.stdout.(*bytes.Buffer).String())
if resp.Error == nil || resp.Error.Code != -32602 {
t.Fatalf("Expected error code -32602")
}
}
func TestMCP_CheckKubesharkStatus(t *testing.T) {
for _, tc := range []struct {
name string
args map[string]any
}{
{"no_config", map[string]any{}},
{"with_namespace", map[string]any{"release_namespace": "custom-ns"}},
} {
t.Run(tc.name, func(t *testing.T) {
resp := parseResponse(t, sendRequest(newTestMCPServer(), "tools/call", 1, mcpCallToolParams{Name: "check_kubeshark_status", Arguments: tc.args}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
content := resp.Result.(map[string]any)["content"].([]any)
if len(content) == 0 || content[0].(map[string]any)["text"].(string) == "" {
t.Error("Expected non-empty response")
}
})
}
}
func newTestMCPServerWithMockBackend(handler http.HandlerFunc) (*mcpServer, *httptest.Server) {
mockServer := httptest.NewServer(handler)
return &mcpServer{httpClient: &http.Client{}, stdin: &bytes.Buffer{}, stdout: &bytes.Buffer{}, hubBaseURL: mockServer.URL, backendInitialized: true}, mockServer
}
type hubToolCallRequest struct {
Tool string `json:"name"`
Arguments map[string]any `json:"arguments"`
}
func newMockHubHandler(t *testing.T, handler func(req hubToolCallRequest) (string, int)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/tools/call" || r.Method != http.MethodPost {
w.WriteHeader(http.StatusNotFound)
return
}
var req hubToolCallRequest
_ = json.NewDecoder(r.Body).Decode(&req)
resp, status := handler(req)
w.WriteHeader(status)
_, _ = w.Write([]byte(resp))
}
}
func TestMCP_ListWorkloads(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(newMockHubHandler(t, func(req hubToolCallRequest) (string, int) {
if req.Tool != "list_workloads" {
t.Errorf("Expected tool 'list_workloads', got %s", req.Tool)
}
return `{"workloads": [{"name": "test-pod"}]}`, http.StatusOK
}))
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads", Arguments: map[string]any{"type": "pod"}}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
text := resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string)
if !strings.Contains(text, "test-pod") {
t.Errorf("Expected 'test-pod' in response")
}
}
func TestMCP_ListAPICalls(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(newMockHubHandler(t, func(req hubToolCallRequest) (string, int) {
if req.Tool != "list_api_calls" {
t.Errorf("Expected tool 'list_api_calls', got %s", req.Tool)
}
return `{"calls": [{"id": "123", "path": "/api/users"}]}`, http.StatusOK
}))
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_api_calls", Arguments: map[string]any{"proto": "http"}}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
if !strings.Contains(resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string), "/api/users") {
t.Error("Expected '/api/users' in response")
}
}
func TestMCP_GetAPICall(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(newMockHubHandler(t, func(req hubToolCallRequest) (string, int) {
if req.Tool != "get_api_call" || req.Arguments["id"] != "abc123" {
t.Errorf("Expected get_api_call with id=abc123")
}
return `{"id": "abc123", "path": "/api/orders"}`, http.StatusOK
}))
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "get_api_call", Arguments: map[string]any{"id": "abc123"}}))
if resp.Error != nil || !strings.Contains(resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string), "abc123") {
t.Error("Expected response containing 'abc123'")
}
}
func TestMCP_GetAPICall_MissingID(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(newMockHubHandler(t, func(req hubToolCallRequest) (string, int) {
return `{"error": "id is required"}`, http.StatusBadRequest
}))
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "get_api_call", Arguments: map[string]any{}}))
if !resp.Result.(map[string]any)["isError"].(bool) {
t.Error("Expected isError=true")
}
}
func TestMCP_GetAPIStats(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(newMockHubHandler(t, func(req hubToolCallRequest) (string, int) {
if req.Tool != "get_api_stats" {
t.Errorf("Expected get_api_stats")
}
return `{"stats": {"total_calls": 1000}}`, http.StatusOK
}))
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "get_api_stats", Arguments: map[string]any{"ns": "prod"}}))
if resp.Error != nil || !strings.Contains(resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string), "total_calls") {
t.Error("Expected 'total_calls' in response")
}
}
func TestMCP_APITools_BackendError(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
})
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads"}))
if !resp.Result.(map[string]any)["isError"].(bool) {
t.Error("Expected isError=true for backend error")
}
}
func TestMCP_APITools_BackendConnectionError(t *testing.T) {
s := &mcpServer{httpClient: &http.Client{}, stdin: &bytes.Buffer{}, stdout: &bytes.Buffer{}, hubBaseURL: "http://localhost:99999", backendInitialized: true}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads"}))
if !resp.Result.(map[string]any)["isError"].(bool) {
t.Error("Expected isError=true for connection error")
}
}
func TestMCP_RunLoop_ParseError(t *testing.T) {
output := &bytes.Buffer{}
s := &mcpServer{httpClient: &http.Client{}, stdin: strings.NewReader("invalid\n"), stdout: output}
s.run()
if resp := parseResponse(t, output.String()); resp.Error == nil || resp.Error.Code != -32700 {
t.Fatalf("Expected error code -32700")
}
}
func TestMCP_RunLoop_MultipleRequests(t *testing.T) {
output := &bytes.Buffer{}
s := &mcpServer{httpClient: &http.Client{}, stdin: strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"ping"}
{"jsonrpc":"2.0","id":2,"method":"ping"}
`), stdout: output}
s.run()
if lines := strings.Split(strings.TrimSpace(output.String()), "\n"); len(lines) != 2 {
t.Fatalf("Expected 2 responses, got %d", len(lines))
}
}
func TestMCP_RunLoop_EmptyLines(t *testing.T) {
output := &bytes.Buffer{}
s := &mcpServer{httpClient: &http.Client{}, stdin: strings.NewReader("\n\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"ping\"}\n"), stdout: output}
s.run()
if lines := strings.Split(strings.TrimSpace(output.String()), "\n"); len(lines) != 1 {
t.Fatalf("Expected 1 response, got %d", len(lines))
}
}
func TestMCP_ResponseFormat(t *testing.T) {
s := newTestMCPServer()
// Numeric ID
if resp := parseResponse(t, sendRequest(s, "ping", 123, nil)); resp.ID != float64(123) || resp.JSONRPC != "2.0" {
t.Errorf("Expected ID 123 and jsonrpc 2.0")
}
// String ID
if resp := parseResponse(t, sendRequest(s, "ping", "str", nil)); resp.ID != "str" {
t.Errorf("Expected ID 'str'")
}
}
func TestMCP_ToolCallResult_ContentFormat(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"data": "test"}`))
})
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads"}))
content := resp.Result.(map[string]any)["content"].([]any)
if len(content) == 0 || content[0].(map[string]any)["type"] != "text" {
t.Error("Expected content with type=text")
}
}
func TestMCP_CommandArgs(t *testing.T) {
// Test start command args building
for _, tc := range []struct {
args map[string]any
expected string
}{
{map[string]any{}, "tap --set headless=true"},
{map[string]any{"pod_regex": "nginx.*"}, "tap nginx.* --set headless=true"},
{map[string]any{"namespaces": "default"}, "tap -n default --set headless=true"},
{map[string]any{"release_namespace": "ks"}, "tap -s ks --set headless=true"},
} {
cmdArgs := []string{"tap"}
if v, _ := tc.args["pod_regex"].(string); v != "" {
cmdArgs = append(cmdArgs, v)
}
if v, _ := tc.args["namespaces"].(string); v != "" {
for ns := range strings.SplitSeq(v, ",") {
cmdArgs = append(cmdArgs, "-n", strings.TrimSpace(ns))
}
}
if v, _ := tc.args["release_namespace"].(string); v != "" {
cmdArgs = append(cmdArgs, "-s", v)
}
cmdArgs = append(cmdArgs, "--set", "headless=true")
if got := strings.Join(cmdArgs, " "); got != tc.expected {
t.Errorf("Expected %q, got %q", tc.expected, got)
}
}
}
func TestMCP_PrettyPrintJSON(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{"key":"value"}`))
})
defer mockServer.Close()
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads"}))
text := resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string)
if !strings.Contains(text, "\n") {
t.Error("Expected pretty-printed JSON")
}
}
func TestMCP_SpecialCharsAndEdgeCases(t *testing.T) {
s, mockServer := newTestMCPServerWithMockBackend(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`{}`))
})
defer mockServer.Close()
// Test special chars, empty args, nil args
for _, args := range []map[string]any{
{"path": "/api?id=123"},
{"id": "abc/123"},
{},
nil,
} {
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{Name: "list_workloads", Arguments: args}))
if resp.Error != nil {
t.Errorf("Unexpected error with args %v: %v", args, resp.Error)
}
}
}
func TestMCP_BackendInitialization_Concurrent(t *testing.T) {
s := newTestMCPServer()
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func() { s.ensureBackendConnection(); done <- true }()
}
for i := 0; i < 10; i++ {
<-done
}
}
func TestMCP_GetFileURL_ProxyMode(t *testing.T) {
s := &mcpServer{
httpClient: &http.Client{},
stdin: &bytes.Buffer{},
stdout: &bytes.Buffer{},
hubBaseURL: "http://127.0.0.1:8899/api/mcp",
backendInitialized: true,
}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{
Name: "get_file_url",
Arguments: map[string]any{"path": "/snapshots/abc/data.pcap"},
}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
text := resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string)
expected := "http://127.0.0.1:8899/api/snapshots/abc/data.pcap"
if text != expected {
t.Errorf("Expected %q, got %q", expected, text)
}
}
func TestMCP_GetFileURL_URLMode(t *testing.T) {
s := &mcpServer{
httpClient: &http.Client{},
stdin: &bytes.Buffer{},
stdout: &bytes.Buffer{},
hubBaseURL: "https://kubeshark.example.com/api/mcp",
backendInitialized: true,
urlMode: true,
directURL: "https://kubeshark.example.com",
}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{
Name: "get_file_url",
Arguments: map[string]any{"path": "/snapshots/xyz/export.pcap"},
}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
text := resp.Result.(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string)
expected := "https://kubeshark.example.com/api/snapshots/xyz/export.pcap"
if text != expected {
t.Errorf("Expected %q, got %q", expected, text)
}
}
func TestMCP_GetFileURL_MissingPath(t *testing.T) {
s := &mcpServer{
httpClient: &http.Client{},
stdin: &bytes.Buffer{},
stdout: &bytes.Buffer{},
hubBaseURL: "http://127.0.0.1:8899/api/mcp",
backendInitialized: true,
}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{
Name: "get_file_url",
Arguments: map[string]any{},
}))
result := resp.Result.(map[string]any)
if !result["isError"].(bool) {
t.Error("Expected isError=true when path is missing")
}
text := result["content"].([]any)[0].(map[string]any)["text"].(string)
if !strings.Contains(text, "path") {
t.Error("Error message should mention 'path'")
}
}
func TestMCP_DownloadFile(t *testing.T) {
fileContent := "test pcap data content"
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/snapshots/abc/data.pcap" {
_, _ = w.Write([]byte(fileContent))
} else {
w.WriteHeader(http.StatusNotFound)
}
}))
defer mockServer.Close()
// Use temp dir for download destination
tmpDir := t.TempDir()
dest := filepath.Join(tmpDir, "downloaded.pcap")
s := &mcpServer{
httpClient: &http.Client{},
stdin: &bytes.Buffer{},
stdout: &bytes.Buffer{},
hubBaseURL: mockServer.URL + "/api/mcp",
backendInitialized: true,
}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{
Name: "download_file",
Arguments: map[string]any{"path": "/snapshots/abc/data.pcap", "dest": dest},
}))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
result := resp.Result.(map[string]any)
if result["isError"] != nil && result["isError"].(bool) {
t.Fatalf("Expected no error, got: %v", result["content"])
}
text := result["content"].([]any)[0].(map[string]any)["text"].(string)
var downloadResult map[string]any
if err := json.Unmarshal([]byte(text), &downloadResult); err != nil {
t.Fatalf("Failed to parse download result JSON: %v", err)
}
if downloadResult["path"] != dest {
t.Errorf("Expected path %q, got %q", dest, downloadResult["path"])
}
if downloadResult["size"].(float64) != float64(len(fileContent)) {
t.Errorf("Expected size %d, got %v", len(fileContent), downloadResult["size"])
}
// Verify the file was actually written
content, err := os.ReadFile(dest)
if err != nil {
t.Fatalf("Failed to read downloaded file: %v", err)
}
if string(content) != fileContent {
t.Errorf("Expected file content %q, got %q", fileContent, string(content))
}
}
func TestMCP_DownloadFile_CustomDest(t *testing.T) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("data"))
}))
defer mockServer.Close()
tmpDir := t.TempDir()
customDest := filepath.Join(tmpDir, "custom-name.pcap")
s := &mcpServer{
httpClient: &http.Client{},
stdin: &bytes.Buffer{},
stdout: &bytes.Buffer{},
hubBaseURL: mockServer.URL + "/api/mcp",
backendInitialized: true,
}
resp := parseResponse(t, sendRequest(s, "tools/call", 1, mcpCallToolParams{
Name: "download_file",
Arguments: map[string]any{"path": "/snapshots/abc/export.pcap", "dest": customDest},
}))
result := resp.Result.(map[string]any)
if result["isError"] != nil && result["isError"].(bool) {
t.Fatalf("Expected no error, got: %v", result["content"])
}
text := result["content"].([]any)[0].(map[string]any)["text"].(string)
var downloadResult map[string]any
if err := json.Unmarshal([]byte(text), &downloadResult); err != nil {
t.Fatalf("Failed to parse download result JSON: %v", err)
}
if downloadResult["path"] != customDest {
t.Errorf("Expected path %q, got %q", customDest, downloadResult["path"])
}
if _, err := os.Stat(customDest); os.IsNotExist(err) {
t.Error("Expected file to exist at custom destination")
}
}
func TestMCP_ToolsList_IncludesFileTools(t *testing.T) {
s := newTestMCPServer()
resp := parseResponse(t, sendRequest(s, "tools/list", 1, nil))
if resp.Error != nil {
t.Fatalf("Unexpected error: %v", resp.Error)
}
tools := resp.Result.(map[string]any)["tools"].([]any)
toolNames := make(map[string]bool)
for _, tool := range tools {
toolNames[tool.(map[string]any)["name"].(string)] = true
}
for _, expected := range []string{"get_file_url", "download_file"} {
if !toolNames[expected] {
t.Errorf("Missing expected tool: %s", expected)
}
}
}
func TestMCP_FullConversation(t *testing.T) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
_, _ = w.Write([]byte(`{"name":"hub","tools":[{"name":"list_workloads","description":"","inputSchema":{}}]}`))
} else if r.URL.Path == "/tools/call" {
_, _ = w.Write([]byte(`{"data":"ok"}`))
}
}))
defer mockServer.Close()
input := `{"jsonrpc":"2.0","id":1,"method":"initialize"}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_workloads","arguments":{}}}
`
output := &bytes.Buffer{}
s := &mcpServer{httpClient: &http.Client{}, stdin: strings.NewReader(input), stdout: output, hubBaseURL: mockServer.URL, backendInitialized: true}
s.run()
lines := strings.Split(strings.TrimSpace(output.String()), "\n")
if len(lines) != 3 { // 3 responses (notification has no response)
t.Errorf("Expected 3 responses, got %d", len(lines))
}
for i, line := range lines {
var resp jsonRPCResponse
if err := json.Unmarshal([]byte(line), &resp); err != nil || resp.Error != nil {
t.Errorf("Response %d: parse error or unexpected error", i)
}
}
}

View File

@@ -2,11 +2,14 @@ package cmd
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
"github.com/creasty/defaults"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
@@ -31,17 +34,23 @@ var pcapDumpCmd = &cobra.Command{
}
}
debugEnabled, _ := cmd.Flags().GetBool("debug")
if debugEnabled {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
log.Debug().Msg("Debug logging enabled")
} else {
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
// Use the current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
log.Error().Err(err).Msg("Error building kubeconfig")
return err
return fmt.Errorf("Error building kubeconfig: %w", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.Error().Err(err).Msg("Error creating Kubernetes client")
return err
return fmt.Errorf("Error creating Kubernetes client: %w", err)
}
// Parse the `--time` flag
@@ -50,19 +59,35 @@ var pcapDumpCmd = &cobra.Command{
if timeIntervalStr != "" {
duration, err := time.ParseDuration(timeIntervalStr)
if err != nil {
log.Error().Err(err).Msg("Invalid time interval")
return err
return fmt.Errorf("Invalid format %w", err)
}
tempCutoffTime := time.Now().Add(-duration)
cutoffTime = &tempCutoffTime
}
// Handle copy operation if the copy string is provided
// Test the dest dir if provided
destDir, _ := cmd.Flags().GetString(configStructs.PcapDest)
if destDir != "" {
info, err := os.Stat(destDir)
if os.IsNotExist(err) {
return fmt.Errorf("Directory does not exist: %s", destDir)
}
if err != nil {
return fmt.Errorf("Error checking dest directory: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("Dest path is not a directory: %s", destDir)
}
tempFile, err := os.CreateTemp(destDir, "write-test-*")
if err != nil {
return fmt.Errorf("Directory %s is not writable", destDir)
}
_ = os.Remove(tempFile.Name())
}
log.Info().Msg("Copying PCAP files")
err = copyPcapFiles(clientset, config, destDir, cutoffTime)
if err != nil {
log.Error().Err(err).Msg("Error copying PCAP files")
return err
}
@@ -81,4 +106,5 @@ func init() {
pcapDumpCmd.Flags().String(configStructs.PcapTime, "", "Time interval (e.g., 10m, 1h) in the past for which the pcaps are copied")
pcapDumpCmd.Flags().String(configStructs.PcapDest, "", "Local destination path for copied PCAP files (can not be used together with --enabled)")
pcapDumpCmd.Flags().String(configStructs.PcapKubeconfig, "", "Path for kubeconfig (if not provided the default location will be checked)")
pcapDumpCmd.Flags().Bool("debug", false, "Enable debug logging")
}

View File

@@ -1,40 +1,46 @@
package cmd
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/kubeshark/gopacket"
"github.com/kubeshark/gopacket/layers"
"github.com/kubeshark/gopacket/pcapgo"
"github.com/rs/zerolog/log"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientk8s "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)
const label = "app.kubeshark.co/app=worker"
const SELF_RESOURCES_PREFIX = "kubeshark-"
const SUFFIX_CONFIG_MAP = "config-map"
const (
label = "app.kubeshark.com/app=worker"
srcDir = "pcapdump"
maxSnaplen uint32 = 262144
maxTimePerFile = time.Minute * 5
)
// NamespaceFiles represents the namespace and the files found in that namespace.
type NamespaceFiles struct {
Namespace string // The namespace in which the files were found
SrcDir string // The source directory from which the files were listed
Files []string // List of files found in the namespace
// PodFileInfo represents information about a pod, its namespace, and associated files
type PodFileInfo struct {
Pod corev1.Pod
SrcDir string
Files []string
CopiedFiles []string
}
// listWorkerPods fetches all worker pods from multiple namespaces
func listWorkerPods(ctx context.Context, clientset *clientk8s.Clientset, namespaces []string) ([]corev1.Pod, error) {
var allPods []corev1.Pod
func listWorkerPods(ctx context.Context, clientset *kubernetes.Clientset, namespaces []string) ([]*PodFileInfo, error) {
var podFileInfos []*PodFileInfo
var errs []error
labelSelector := label
for _, namespace := range namespaces {
@@ -43,142 +49,30 @@ func listWorkerPods(ctx context.Context, clientset *clientk8s.Clientset, namespa
LabelSelector: labelSelector,
})
if err != nil {
return nil, fmt.Errorf("failed to list worker pods in namespace %s: %w", namespace, err)
}
// Accumulate the pods
allPods = append(allPods, pods.Items...)
}
return allPods, nil
}
// listFilesInPodDir lists all files in the specified directory inside the pod across multiple namespaces
func listFilesInPodDir(ctx context.Context, clientset *clientk8s.Clientset, config *rest.Config, podName string, namespaces []string, configMapName, configMapKey string, cutoffTime *time.Time) ([]NamespaceFiles, error) {
var namespaceFilesList []NamespaceFiles
for _, namespace := range namespaces {
// Attempt to get the ConfigMap in the current namespace
configMap, err := clientset.CoreV1().ConfigMaps(namespace).Get(ctx, configMapName, metav1.GetOptions{})
if err != nil {
errs = append(errs, fmt.Errorf("failed to list worker pods in namespace %s: %w", namespace, err))
continue
}
// Check if the source directory exists in the ConfigMap
srcDir, ok := configMap.Data[configMapKey]
if !ok || srcDir == "" {
log.Error().Msgf("source directory not found in ConfigMap %s in namespace %s", configMapName, namespace)
continue
}
// Attempt to get the pod in the current namespace
pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
log.Error().Err(err).Msgf("failed to get pod %s in namespace %s", podName, namespace)
continue
}
nodeName := pod.Spec.NodeName
srcFilePath := filepath.Join("data", nodeName, srcDir)
cmd := []string{"ls", srcFilePath}
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
SubResource("exec").
Param("container", "sniffer").
Param("stdout", "true").
Param("stderr", "true").
Param("command", cmd[0]).
Param("command", cmd[1])
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
log.Error().Err(err).Msgf("failed to initialize executor for pod %s in namespace %s", podName, namespace)
continue
}
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
// Execute the command to list files
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdout: &stdoutBuf,
Stderr: &stderrBuf,
})
if err != nil {
log.Error().Err(err).Msgf("error listing files in pod %s in namespace %s: %s", podName, namespace, stderrBuf.String())
continue
}
// Split the output (file names) into a list
files := strings.Split(strings.TrimSpace(stdoutBuf.String()), "\n")
if len(files) == 0 {
log.Info().Msgf("No files found in directory %s in pod %s", srcFilePath, podName)
continue
}
var filteredFiles []string
// Filter files based on cutoff time if provided
for _, file := range files {
if cutoffTime != nil {
parts := strings.Split(file, "-")
if len(parts) < 2 {
log.Warn().Msgf("Skipping file with invalid format: %s", file)
continue
}
timestampStr := parts[len(parts)-2] + parts[len(parts)-1][:6] // Extract YYYYMMDDHHMMSS
fileTime, err := time.Parse("20060102150405", timestampStr)
if err != nil {
log.Warn().Err(err).Msgf("Skipping file with unparsable timestamp: %s", file)
continue
}
if fileTime.Before(*cutoffTime) {
continue
}
}
// Add file to filtered list
filteredFiles = append(filteredFiles, file)
}
if len(filteredFiles) > 0 {
namespaceFilesList = append(namespaceFilesList, NamespaceFiles{
Namespace: namespace,
SrcDir: srcDir,
Files: filteredFiles,
for _, pod := range pods.Items {
podFileInfos = append(podFileInfos, &PodFileInfo{
Pod: pod,
})
}
}
if len(namespaceFilesList) == 0 {
return nil, fmt.Errorf("no files found in pod %s across the provided namespaces", podName)
}
return namespaceFilesList, nil
return podFileInfos, errors.Join(errs...)
}
// copyFileFromPod copies a single file from a pod to a local destination
func copyFileFromPod(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, podName, namespace, srcDir, srcFile, destFile string) error {
// Get the pod to retrieve its node name
pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get pod %s in namespace %s: %w", podName, namespace, err)
}
// listFilesInPodDir lists all files in the specified directory inside the pod across multiple namespaces
func listFilesInPodDir(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, pod *PodFileInfo, cutoffTime *time.Time) error {
nodeName := pod.Pod.Spec.NodeName
srcFilePath := filepath.Join("data", nodeName, srcDir)
// Construct the complete path using /data, the node name, srcDir, and srcFile
nodeName := pod.Spec.NodeName
srcFilePath := filepath.Join("data", nodeName, srcDir, srcFile)
// Execute the `cat` command to read the file at the srcFilePath
cmd := []string{"cat", srcFilePath}
cmd := []string{"ls", srcFilePath}
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(podName).
Namespace(namespace).
Name(pod.Pod.Name).
Namespace(pod.Pod.Namespace).
SubResource("exec").
Param("container", "sniffer").
Param("stdout", "true").
@@ -188,7 +82,81 @@ func copyFileFromPod(ctx context.Context, clientset *kubernetes.Clientset, confi
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to initialize executor for pod %s in namespace %s: %w", podName, namespace, err)
return err
}
var stdoutBuf bytes.Buffer
var stderrBuf bytes.Buffer
// Execute the command to list files
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdout: &stdoutBuf,
Stderr: &stderrBuf,
})
if err != nil {
return err
}
// Split the output (file names) into a list
files := strings.Split(strings.TrimSpace(stdoutBuf.String()), "\n")
if len(files) == 0 {
// No files were found in the target dir for this pod
return nil
}
var filteredFiles []string
var fileProcessingErrs []error
// Filter files based on cutoff time if provided
for _, file := range files {
if cutoffTime != nil {
parts := strings.Split(file, "-")
if len(parts) < 2 {
continue
}
timestampStr := parts[len(parts)-2] + parts[len(parts)-1][:6] // Extract YYYYMMDDHHMMSS
fileTime, err := time.Parse("20060102150405", timestampStr)
if err != nil {
fileProcessingErrs = append(fileProcessingErrs, fmt.Errorf("failed parse file timestamp %s: %w", file, err))
continue
}
if fileTime.Before(*cutoffTime) {
continue
}
}
// Add file to filtered list
filteredFiles = append(filteredFiles, file)
}
pod.SrcDir = srcDir
pod.Files = filteredFiles
return errors.Join(fileProcessingErrs...)
}
// copyFileFromPod copies a single file from a pod to a local destination
func copyFileFromPod(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, pod *PodFileInfo, srcFile, destFile string) error {
// Construct the complete path using /data, the node name, srcDir, and srcFile
nodeName := pod.Pod.Spec.NodeName
srcFilePath := filepath.Join("data", nodeName, srcDir, srcFile)
// Execute the `cat` command to read the file at the srcFilePath
cmd := []string{"cat", srcFilePath}
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(pod.Pod.Name).
Namespace(pod.Pod.Namespace).
SubResource("exec").
Param("container", "sniffer").
Param("stdout", "true").
Param("stderr", "true").
Param("command", cmd[0]).
Param("command", cmd[1])
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
if err != nil {
return fmt.Errorf("failed to initialize executor for pod %s in namespace %s: %w", pod.Pod.Name, pod.Pod.Namespace, err)
}
// Create the local file to write the content to
@@ -207,7 +175,7 @@ func copyFileFromPod(ctx context.Context, clientset *kubernetes.Clientset, confi
Stderr: &stderrBuf,
})
if err != nil {
return fmt.Errorf("error copying file from pod %s in namespace %s: %s", podName, namespace, stderrBuf.String())
return err
}
return nil
@@ -217,129 +185,186 @@ func mergePCAPs(outputFile string, inputFiles []string) error {
// Create the output file
f, err := os.Create(outputFile)
if err != nil {
return err
return fmt.Errorf("failed to create output file: %w", err)
}
defer f.Close()
// Create a pcap writer for the output file
writer := pcapgo.NewWriter(f)
err = writer.WriteFileHeader(65536, layers.LinkTypeEthernet) // Snapshot length and LinkType
bufWriter := bufio.NewWriterSize(f, 4*1024*1024)
defer bufWriter.Flush()
// Create the PCAP writer
writer := pcapgo.NewWriter(bufWriter)
err = writer.WriteFileHeader(maxSnaplen, 1)
if err != nil {
return err
return fmt.Errorf("failed to write PCAP file header: %w", err)
}
var mergingErrs []error
for _, inputFile := range inputFiles {
log.Info().Msgf("Merging %s int %s", inputFile, outputFile)
// Open each input file
// Open the input file
file, err := os.Open(inputFile)
if err != nil {
log.Error().Err(err).Msgf("Failed to open %v", inputFile)
mergingErrs = append(mergingErrs, fmt.Errorf("failed to open %s: %w", inputFile, err))
continue
}
defer file.Close()
fileInfo, err := file.Stat()
if err != nil {
mergingErrs = append(mergingErrs, fmt.Errorf("failed to stat file %s: %w", inputFile, err))
file.Close()
continue
}
if fileInfo.Size() == 0 {
// Skip empty files
log.Debug().Msgf("Skipped empty file: %s", inputFile)
file.Close()
continue
}
// Create the PCAP reader for the input file
reader, err := pcapgo.NewReader(file)
if err != nil {
log.Error().Err(err).Msgf("Failed to create pcapng reader for %v", file.Name())
mergingErrs = append(mergingErrs, fmt.Errorf("failed to create pcapng reader for %v: %w", file.Name(), err))
file.Close()
continue
}
// Create the packet source
packetSource := gopacket.NewPacketSource(reader, layers.LinkTypeEthernet)
for packet := range packetSource.Packets() {
err := writer.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
for {
// Read packet data
data, ci, err := reader.ReadPacketData()
if err != nil {
log.Error().Err(err).Msgf("Failed to write packet to %v", outputFile)
continue
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
break
}
mergingErrs = append(mergingErrs, fmt.Errorf("error reading packet from file %s: %w", file.Name(), err))
break
}
// Write the packet to the output file
err = writer.WritePacket(ci, data)
if err != nil {
log.Error().Err(err).Msgf("Error writing packet to output file")
mergingErrs = append(mergingErrs, fmt.Errorf("error writing packet to output file: %w", err))
break
}
}
file.Close()
}
log.Debug().Err(errors.Join(mergingErrs...))
return nil
}
// copyPcapFiles function for copying the PCAP files from the worker pods
func copyPcapFiles(clientset *kubernetes.Clientset, config *rest.Config, destDir string, cutoffTime *time.Time) error {
kubernetesProvider, err := getKubernetesProviderForCli(false, false)
// List all namespaces
namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
if err != nil {
log.Error().Err(err).Send()
return err
}
targetNamespaces := kubernetesProvider.GetNamespaces()
var targetNamespaces []string
for _, ns := range namespaceList.Items {
targetNamespaces = append(targetNamespaces, ns.Name)
}
// List worker pods
// List all worker pods
workerPods, err := listWorkerPods(context.Background(), clientset, targetNamespaces)
if err != nil {
log.Error().Err(err).Msg("Error listing worker pods")
return err
}
var currentFiles []string
// Iterate over each pod to get the PCAP directory from config and copy files
for _, pod := range workerPods {
// Get the list of NamespaceFiles (files per namespace) and their source directories
namespaceFiles, err := listFilesInPodDir(context.Background(), clientset, config, pod.Name, targetNamespaces, SELF_RESOURCES_PREFIX+SUFFIX_CONFIG_MAP, "PCAP_SRC_DIR", cutoffTime)
if err != nil {
log.Error().Err(err).Msgf("Error listing files in pod %s", pod.Name)
continue
if len(workerPods) == 0 {
return err
}
log.Debug().Err(err).Msg("error while listing worker pods")
}
// Copy each file from the pod to the local destination for each namespace
for _, nsFiles := range namespaceFiles {
for _, file := range nsFiles.Files {
var wg sync.WaitGroup
// Launch a goroutine for each pod
for _, pod := range workerPods {
wg.Add(1)
go func(pod *PodFileInfo) {
defer wg.Done()
// List files for the current pod
err := listFilesInPodDir(context.Background(), clientset, config, pod, cutoffTime)
if err != nil {
log.Debug().Err(err).Msgf("error listing files in pod %s", pod.Pod.Name)
return
}
// Copy files from the pod
for _, file := range pod.Files {
destFile := filepath.Join(destDir, file)
// Pass the correct namespace and related details to the function
err = copyFileFromPod(context.Background(), clientset, config, pod.Name, nsFiles.Namespace, nsFiles.SrcDir, file, destFile)
// Add a timeout context for file copy
ctx, cancel := context.WithTimeout(context.Background(), maxTimePerFile)
err := copyFileFromPod(ctx, clientset, config, pod, file, destFile)
cancel()
if err != nil {
log.Error().Err(err).Msgf("Error copying file from pod %s in namespace %s", pod.Name, nsFiles.Namespace)
} else {
log.Info().Msgf("Copied %s from %s to %s", file, pod.Name, destFile)
log.Debug().Err(err).Msgf("error copying file %s from pod %s in namespace %s", file, pod.Pod.Name, pod.Pod.Namespace)
continue
}
currentFiles = append(currentFiles, destFile)
log.Info().Msgf("Copied file %s from pod %s to %s", file, pod.Pod.Name, destFile)
pod.CopiedFiles = append(pod.CopiedFiles, destFile)
}
}
}(pod)
}
if len(currentFiles) == 0 {
log.Error().Msgf("No files to merge")
// Wait for all goroutines to complete
wg.Wait()
var copiedFiles []string
for _, pod := range workerPods {
copiedFiles = append(copiedFiles, pod.CopiedFiles...)
}
if len(copiedFiles) == 0 {
log.Info().Msg("No pcaps available to copy on the workers")
return nil
// continue
}
// Generate a temporary filename based on the first file
tempMergedFile := currentFiles[0] + "_temp"
// Generate a temporary filename for the merged file
tempMergedFile := copiedFiles[0] + "_temp"
// Merge the PCAPs into the temporary file
err = mergePCAPs(tempMergedFile, currentFiles)
// Merge PCAP files
err = mergePCAPs(tempMergedFile, copiedFiles)
if err != nil {
log.Error().Err(err).Msgf("Error merging files")
return err
// continue
os.Remove(tempMergedFile)
return fmt.Errorf("error merging files: %w", err)
}
// Remove the original files after merging
for _, file := range currentFiles {
err := os.Remove(file)
if err != nil {
log.Error().Err(err).Msgf("Error removing file %s", file)
for _, file := range copiedFiles {
if err = os.Remove(file); err != nil {
log.Debug().Err(err).Msgf("error removing file %s", file)
}
}
// Rename the temp file to the final name (removing "_temp")
finalMergedFile := strings.TrimSuffix(tempMergedFile, "_temp")
clusterID, err := getClusterID(clientset)
if err != nil {
return fmt.Errorf("failed to get cluster ID: %w", err)
}
timestamp := time.Now().Format("2006-01-02_15-04")
// Rename the temp file to the final name
finalMergedFile := filepath.Join(destDir, fmt.Sprintf("%s-%s.pcap", clusterID, timestamp))
err = os.Rename(tempMergedFile, finalMergedFile)
if err != nil {
log.Error().Err(err).Msgf("Error renaming merged file %s", tempMergedFile)
// continue
return err
}
log.Info().Msgf("Merged file created: %s", finalMergedFile)
return nil
}
func getClusterID(clientset *kubernetes.Clientset) (string, error) {
namespace, err := clientset.CoreV1().Namespaces().Get(context.TODO(), "kube-system", metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to get kube-system namespace UID: %w", err)
}
return string(namespace.UID), nil
}

View File

@@ -33,6 +33,7 @@ func init() {
rootCmd.PersistentFlags().StringSlice(config.SetCommandName, []string{}, fmt.Sprintf("Override values using --%s", config.SetCommandName))
rootCmd.PersistentFlags().BoolP(config.DebugFlag, "d", false, "Enable debug mode")
rootCmd.PersistentFlags().String(config.ConfigPathFlag, "", fmt.Sprintf("Set the config path, default: %s", config.GetConfigFilePath(nil)))
}
// Execute adds all child commands to the root command and sets flags appropriately.

View File

@@ -123,7 +123,7 @@ func createScript(provider *kubernetes.Provider, script misc.ConfigMapScript) (i
}
if k8serrors.IsConflict(err) {
log.Warn().Err(err).Msg("Conflict detected, retrying update...")
log.Debug().Err(err).Msg("Conflict detected, retrying update...")
time.Sleep(500 * time.Millisecond)
continue
}
@@ -332,23 +332,29 @@ func watchConfigMap(ctx context.Context, provider *kubernetes.Provider) {
continue
}
for event := range watcher.ResultChan() {
select {
case <-ctx.Done():
log.Info().Msg("ConfigMap watcher loop exiting gracefully.")
watcher.Stop()
return
default:
// Create a goroutine to process events
watcherClosed := make(chan struct{})
go func() {
defer close(watcherClosed)
for event := range watcher.ResultChan() {
if event.Type == watch.Added {
log.Info().Msg("ConfigMap created or modified")
runScriptsSync(provider)
} else if event.Type == watch.Deleted {
log.Warn().Msg("ConfigMap deleted, waiting for recreation...")
watcher.Stop()
break
}
}
}()
// Wait for either context cancellation or watcher completion
select {
case <-ctx.Done():
watcher.Stop()
log.Info().Msg("ConfigMap watcher stopping due to context cancellation")
return
case <-watcherClosed:
log.Info().Msg("Watcher closed, restarting...")
}
time.Sleep(5 * time.Second)

View File

@@ -58,8 +58,9 @@ func init() {
tapCmd.Flags().Bool(configStructs.DryRunLabel, defaultTapConfig.DryRun, "Preview of all pods matching the regex, without tapping them")
tapCmd.Flags().Bool(configStructs.ServiceMeshLabel, defaultTapConfig.ServiceMesh, "Capture the encrypted traffic if the cluster is configured with a service mesh and with mTLS")
tapCmd.Flags().Bool(configStructs.TlsLabel, defaultTapConfig.Tls, "Capture the traffic that's encrypted with OpenSSL or Go crypto/tls libraries")
tapCmd.Flags().Bool(configStructs.IgnoreTaintedLabel, defaultTapConfig.IgnoreTainted, "Ignore tainted pods while running Worker DaemonSet")
tapCmd.Flags().Bool(configStructs.IngressEnabledLabel, defaultTapConfig.Ingress.Enabled, "Enable Ingress")
tapCmd.Flags().Bool(configStructs.TelemetryEnabledLabel, defaultTapConfig.Telemetry.Enabled, "Enable/disable Telemetry")
tapCmd.Flags().Bool(configStructs.ResourceGuardEnabledLabel, defaultTapConfig.ResourceGuard.Enabled, "Enable/disable resource guard")
tapCmd.Flags().Bool(configStructs.WatchdogEnabled, defaultTapConfig.Watchdog.Enabled, "Enable/disable watchdog")
tapCmd.Flags().String(configStructs.HelmChartPathLabel, defaultTapConfig.Release.HelmChartPath, "Path to a local Helm chart folder (overrides the remote Helm repo)")
}

View File

@@ -40,9 +40,11 @@ type Readiness struct {
}
var ready *Readiness
var proxyOnce sync.Once
func tap() {
ready = &Readiness{}
proxyOnce = sync.Once{}
state.startTime = time.Now()
log.Info().Str("registry", config.Config.Tap.Docker.Registry).Str("tag", config.Config.Tap.Docker.Tag).Msg("Using Docker:")
@@ -147,11 +149,21 @@ func printNoPodsFoundSuggestion(targetNamespaces []string) {
log.Warn().Msg(fmt.Sprintf("Did not find any currently running pods that match the regex argument, %s will automatically target matching pods if any are created later%s", misc.Software, suggestionStr))
}
func isPodReady(pod *core.Pod) bool {
for _, condition := range pod.Status.Conditions {
if condition.Type == core.PodReady {
return condition.Status == core.ConditionTrue
}
}
return false
}
func watchHubPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, cancel context.CancelFunc) {
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s", kubernetes.HubPodName))
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.Tap.Release.Namespace}, podWatchHelper)
isPodReady := false
podReady := false
podRunning := false
timeAfter := time.After(120 * time.Second)
for {
@@ -183,26 +195,30 @@ func watchHubPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, c
Interface("containers-statuses", modifiedPod.Status.ContainerStatuses).
Msg("Watching pod.")
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true
if isPodReady(modifiedPod) && !podReady {
podReady = true
ready.Lock()
ready.Hub = true
ready.Unlock()
log.Info().Str("pod", kubernetes.HubPodName).Msg("Ready.")
} else if modifiedPod.Status.Phase == core.PodRunning && !podRunning {
podRunning = true
log.Info().Str("pod", kubernetes.HubPodName).Msg("Waiting for readiness...")
}
ready.Lock()
proxyDone := ready.Proxy
hubPodReady := ready.Hub
frontPodReady := ready.Front
ready.Unlock()
if !proxyDone && hubPodReady && frontPodReady {
ready.Lock()
ready.Proxy = true
ready.Unlock()
postFrontStarted(ctx, kubernetesProvider, cancel)
if hubPodReady && frontPodReady {
proxyOnce.Do(func() {
ready.Lock()
ready.Proxy = true
ready.Unlock()
postFrontStarted(ctx, kubernetesProvider, cancel)
})
}
case kubernetes.EventBookmark:
break
@@ -223,7 +239,7 @@ func watchHubPod(ctx context.Context, kubernetesProvider *kubernetes.Provider, c
cancel()
case <-timeAfter:
if !isPodReady {
if !podReady {
log.Error().
Str("pod", kubernetes.HubPodName).
Msg("Pod was not ready in time.")
@@ -242,7 +258,8 @@ func watchFrontPod(ctx context.Context, kubernetesProvider *kubernetes.Provider,
podExactRegex := regexp.MustCompile(fmt.Sprintf("^%s", kubernetes.FrontPodName))
podWatchHelper := kubernetes.NewPodWatchHelper(kubernetesProvider, podExactRegex)
eventChan, errorChan := kubernetes.FilteredWatch(ctx, podWatchHelper, []string{config.Config.Tap.Release.Namespace}, podWatchHelper)
isPodReady := false
podReady := false
podRunning := false
timeAfter := time.After(120 * time.Second)
for {
@@ -274,25 +291,29 @@ func watchFrontPod(ctx context.Context, kubernetesProvider *kubernetes.Provider,
Interface("containers-statuses", modifiedPod.Status.ContainerStatuses).
Msg("Watching pod.")
if modifiedPod.Status.Phase == core.PodRunning && !isPodReady {
isPodReady = true
if isPodReady(modifiedPod) && !podReady {
podReady = true
ready.Lock()
ready.Front = true
ready.Unlock()
log.Info().Str("pod", kubernetes.FrontPodName).Msg("Ready.")
} else if modifiedPod.Status.Phase == core.PodRunning && !podRunning {
podRunning = true
log.Info().Str("pod", kubernetes.FrontPodName).Msg("Waiting for readiness...")
}
ready.Lock()
proxyDone := ready.Proxy
hubPodReady := ready.Hub
frontPodReady := ready.Front
ready.Unlock()
if !proxyDone && hubPodReady && frontPodReady {
ready.Lock()
ready.Proxy = true
ready.Unlock()
postFrontStarted(ctx, kubernetesProvider, cancel)
if hubPodReady && frontPodReady {
proxyOnce.Do(func() {
ready.Lock()
ready.Proxy = true
ready.Unlock()
postFrontStarted(ctx, kubernetesProvider, cancel)
})
}
case kubernetes.EventBookmark:
break
@@ -312,7 +333,7 @@ func watchFrontPod(ctx context.Context, kubernetesProvider *kubernetes.Provider,
Msg("Failed creating pod.")
case <-timeAfter:
if !isPodReady {
if !podReady {
log.Error().
Str("pod", kubernetes.FrontPodName).
Msg("Pod was not ready in time.")
@@ -429,9 +450,6 @@ func postFrontStarted(ctx context.Context, kubernetesProvider *kubernetes.Provid
watchScripts(ctx, kubernetesProvider, false)
}
if config.Config.Scripting.Console {
go runConsoleWithoutProxy()
}
}
func updateConfig(kubernetesProvider *kubernetes.Provider) {

View File

@@ -28,6 +28,7 @@ const (
FieldNameTag = "yaml"
ReadonlyTag = "readonly"
DebugFlag = "debug"
ConfigPathFlag = "config-path"
)
var (
@@ -57,6 +58,7 @@ func InitConfig(cmd *cobra.Command) error {
"pro",
"manifests",
"license",
"mcp",
}, cmd.Use) {
go version.CheckNewerVersion()
}
@@ -82,7 +84,7 @@ func InitConfig(cmd *cobra.Command) error {
return err
}
ConfigFilePath = path.Join(misc.GetDotFolderPath(), "config.yaml")
ConfigFilePath = GetConfigFilePath(cmd)
if err := loadConfigFile(&Config, utils.Contains([]string{
"manifests",
"license",
@@ -134,21 +136,44 @@ func WriteConfig(config *ConfigStruct) error {
return nil
}
func loadConfigFile(config *ConfigStruct, silent bool) error {
func GetConfigFilePath(cmd *cobra.Command) string {
defaultConfigPath := path.Join(misc.GetDotFolderPath(), "config.yaml")
cwd, err := os.Getwd()
if err != nil {
return err
return defaultConfigPath
}
if cmd != nil {
configPathOverride, err := cmd.Flags().GetString(ConfigPathFlag)
if err == nil {
if configPathOverride != "" {
resolvedConfigPath, err := filepath.Abs(configPathOverride)
if err != nil {
log.Error().Err(err).Msg("--config-path flag path cannot be resolved")
} else {
return resolvedConfigPath
}
}
} else {
log.Error().Err(err).Msg("--config-path flag parser error")
}
}
cwdConfig := filepath.Join(cwd, fmt.Sprintf("%s.yaml", misc.Program))
reader, err := os.Open(cwdConfig)
if err != nil {
reader, err = os.Open(ConfigFilePath)
if err != nil {
return err
}
return defaultConfigPath
} else {
ConfigFilePath = cwdConfig
reader.Close()
return cwdConfig
}
}
func loadConfigFile(config *ConfigStruct, silent bool) error {
reader, err := os.Open(ConfigFilePath)
if err != nil {
return err
}
defer reader.Close()
@@ -176,9 +201,14 @@ func initFlag(f *pflag.Flag) {
flagPath = append(flagPath, strings.Split(f.Name, "-")...)
flagPathJoined := strings.Join(flagPath, ".")
if strings.HasSuffix(flagPathJoined, ".config.path") {
return
}
sliceValue, isSliceValue := f.Value.(pflag.SliceValue)
if !isSliceValue {
if err := mergeFlagValue(configElemValue, flagPath, strings.Join(flagPath, "."), f.Value.String()); err != nil {
if err := mergeFlagValue(configElemValue, flagPath, flagPathJoined, f.Value.String()); err != nil {
log.Warn().Err(err).Send()
}
return
@@ -191,7 +221,7 @@ func initFlag(f *pflag.Flag) {
return
}
if err := mergeFlagValues(configElemValue, flagPath, strings.Join(flagPath, "."), sliceValue.GetSlice()); err != nil {
if err := mergeFlagValues(configElemValue, flagPath, flagPathJoined, sliceValue.GetSlice()); err != nil {
log.Warn().Err(err).Send()
}
}

View File

@@ -16,61 +16,107 @@ const (
func CreateDefaultConfig() ConfigStruct {
return ConfigStruct{
Tap: configStructs.TapConfig{
NodeSelectorTerms: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: v1.NodeSelectorOpIn,
Values: []string{"linux"},
NodeSelectorTerms: configStructs.NodeSelectorTermsConfig{
Workers: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: v1.NodeSelectorOpIn,
Values: []string{"linux"},
},
},
},
},
Hub: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: v1.NodeSelectorOpIn,
Values: []string{"linux"},
},
},
},
},
Front: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: v1.NodeSelectorOpIn,
Values: []string{"linux"},
},
},
},
},
Dex: []v1.NodeSelectorTerm{
{
MatchExpressions: []v1.NodeSelectorRequirement{
{
Key: "kubernetes.io/os",
Operator: v1.NodeSelectorOpIn,
Values: []string{"linux"},
},
},
},
},
},
Capabilities: configStructs.CapabilitiesConfig{
NetworkCapture: []string{
// NET_RAW is required to listen the network traffic
"NET_RAW",
// NET_ADMIN is required to listen the network traffic
"NET_ADMIN",
Tolerations: configStructs.TolerationsConfig{
Workers: []v1.Toleration{
{
Effect: v1.TaintEffect("NoExecute"),
Operator: v1.TolerationOpExists,
},
},
ServiceMeshCapture: []string{
// SYS_ADMIN is required to read /proc/PID/net/ns + to install eBPF programs (kernel < 5.8)
"SYS_ADMIN",
// SYS_PTRACE is required to set netns to other process + to open libssl.so of other process
"SYS_PTRACE",
// DAC_OVERRIDE is required to read /proc/PID/environ
"DAC_OVERRIDE",
},
EBPFCapture: []string{
// SYS_ADMIN is required to read /proc/PID/net/ns + to install eBPF programs (kernel < 5.8)
"SYS_ADMIN",
// SYS_PTRACE is required to set netns to other process + to open libssl.so of other process
"SYS_PTRACE",
// SYS_RESOURCE is required to change rlimits for eBPF
"SYS_RESOURCE",
// IPC_LOCK is required for ebpf perf buffers allocations after some amount of size buffer size:
// https://github.com/kubeshark/tracer/blob/13e24725ba8b98216dd0e553262e6d9c56dce5fa/main.go#L82)
"IPC_LOCK",
},
SecurityContext: configStructs.SecurityContextConfig{
Privileged: true,
// Capabilities used only when running in unprivileged mode
Capabilities: configStructs.CapabilitiesConfig{
NetworkCapture: []string{
// NET_RAW is required to listen the network traffic
"NET_RAW",
// NET_ADMIN is required to listen the network traffic
"NET_ADMIN",
},
ServiceMeshCapture: []string{
// SYS_ADMIN is required to read /proc/PID/net/ns + to install eBPF programs (kernel < 5.8)
"SYS_ADMIN",
// SYS_PTRACE is required to set netns to other process + to open libssl.so of other process
"SYS_PTRACE",
// DAC_OVERRIDE is required to read /proc/PID/environ
"DAC_OVERRIDE",
},
EBPFCapture: []string{
// SYS_ADMIN is required to read /proc/PID/net/ns + to install eBPF programs (kernel < 5.8)
"SYS_ADMIN",
// SYS_PTRACE is required to set netns to other process + to open libssl.so of other process
"SYS_PTRACE",
// SYS_RESOURCE is required to change rlimits for eBPF
"SYS_RESOURCE",
// IPC_LOCK is required for ebpf perf buffers allocations after some amount of size buffer size:
// https://github.com/kubeshark/tracer/blob/13e24725ba8b98216dd0e553262e6d9c56dce5fa/main.go#L82)
"IPC_LOCK",
},
},
},
Auth: configStructs.AuthConfig{
Saml: configStructs.SamlConfig{
RoleAttribute: "role",
Roles: map[string]configStructs.Role{
"admin": {
Filter: "",
CanDownloadPCAP: true,
CanUseScripting: true,
ScriptingPermissions: configStructs.ScriptingPermissions{
CanSave: true,
CanActivate: true,
CanDelete: true,
},
CanUpdateTargetedPods: true,
CanStopTrafficCapturing: true,
ShowAdminConsoleLink: true,
RolesClaim: "role",
Roles: map[string]configStructs.Role{
"admin": {
Filter: "",
CanDownloadPCAP: true,
CanUseScripting: true,
ScriptingPermissions: configStructs.ScriptingPermissions{
CanSave: true,
CanActivate: true,
CanDelete: true,
},
CanUpdateTargetedPods: true,
CanStopTrafficCapturing: true,
CanControlDissection: true,
ShowAdminConsoleLink: true,
},
},
},
@@ -80,14 +126,44 @@ func CreateDefaultConfig() ConfigStruct {
"http",
"icmp",
"kafka",
"mongodb",
"mysql",
"postgresql",
"redis",
"sctp",
"syscall",
// "sctp",
// "syscall",
// "tcp",
// "udp",
"ws",
// "tlsx",
"tlsx",
"ldap",
"radius",
"diameter",
"udp-flow",
"tcp-flow",
"udp-conn",
"tcp-conn",
},
PortMapping: configStructs.PortMapping{
HTTP: []uint16{80, 443, 8080},
AMQP: []uint16{5671, 5672},
KAFKA: []uint16{9092},
MONGODB: []uint16{27017},
MYSQL: []uint16{3306},
POSTGRESQL: []uint16{5432},
REDIS: []uint16{6379},
LDAP: []uint16{389},
DIAMETER: []uint16{3868},
},
Dashboard: configStructs.DashboardConfig{
CompleteStreamingEnabled: true,
ClusterWideMapEnabled: false,
},
Capture: configStructs.CaptureConfig{
Dissection: configStructs.DissectionConfig{
Enabled: true,
StopAfter: "5m",
},
},
},
}
@@ -103,22 +179,24 @@ type ManifestsConfig struct {
}
type ConfigStruct struct {
Tap configStructs.TapConfig `yaml:"tap" json:"tap"`
Logs configStructs.LogsConfig `yaml:"logs" json:"logs"`
Config configStructs.ConfigConfig `yaml:"config,omitempty" json:"config,omitempty"`
PcapDump configStructs.PcapDumpConfig `yaml:"pcapdump" json:"pcapdump"`
Kube KubeConfig `yaml:"kube" json:"kube"`
DumpLogs bool `yaml:"dumpLogs" json:"dumpLogs" default:"false"`
HeadlessMode bool `yaml:"headless" json:"headless" default:"false"`
License string `yaml:"license" json:"license" default:""`
CloudLicenseEnabled bool `yaml:"cloudLicenseEnabled" json:"cloudLicenseEnabled" default:"true"`
SupportChatEnabled bool `yaml:"supportChatEnabled" json:"supportChatEnabled" default:"true"`
InternetConnectivity bool `yaml:"internetConnectivity" json:"internetConnectivity" default:"true"`
DissectorsUpdatingEnabled bool `yaml:"dissectorsUpdatingEnabled" json:"dissectorsUpdatingEnabled" default:"true"`
Scripting configStructs.ScriptingConfig `yaml:"scripting" json:"scripting"`
Manifests ManifestsConfig `yaml:"manifests,omitempty" json:"manifests,omitempty"`
Timezone string `yaml:"timezone" json:"timezone"`
LogLevel string `yaml:"logLevel" json:"logLevel" default:"warning"`
Tap configStructs.TapConfig `yaml:"tap" json:"tap"`
Logs configStructs.LogsConfig `yaml:"logs" json:"logs"`
Config configStructs.ConfigConfig `yaml:"config,omitempty" json:"config,omitempty"`
PcapDump configStructs.PcapDumpConfig `yaml:"pcapdump" json:"pcapdump"`
Kube KubeConfig `yaml:"kube" json:"kube"`
DumpLogs bool `yaml:"dumpLogs" json:"dumpLogs" default:"false"`
HeadlessMode bool `yaml:"headless" json:"headless" default:"false"`
License string `yaml:"license" json:"license" default:""`
CloudApiUrl string `yaml:"cloudApiUrl" json:"cloudApiUrl" default:"https://api.kubeshark.com"`
CloudLicenseEnabled bool `yaml:"cloudLicenseEnabled" json:"cloudLicenseEnabled" default:"true"`
DemoModeEnabled bool `yaml:"demoModeEnabled" json:"demoModeEnabled" default:"false"`
SupportChatEnabled bool `yaml:"supportChatEnabled" json:"supportChatEnabled" default:"false"`
BetaEnabled bool `yaml:"betaEnabled" json:"betaEnabled" default:"false"`
InternetConnectivity bool `yaml:"internetConnectivity" json:"internetConnectivity" default:"true"`
Scripting configStructs.ScriptingConfig `yaml:"scripting" json:"scripting"`
Manifests ManifestsConfig `yaml:"manifests,omitempty" json:"manifests,omitempty"`
Timezone string `yaml:"timezone" json:"timezone"`
LogLevel string `yaml:"logLevel" json:"logLevel" default:"warning"`
}
func (config *ConfigStruct) ImagePullPolicy() v1.PullPolicy {

View File

@@ -12,6 +12,7 @@ import (
)
type ScriptingConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
Env map[string]interface{} `yaml:"env" json:"env" default:"{}"`
Source string `yaml:"source" json:"source" default:""`
Sources []string `yaml:"sources" json:"sources" default:"[]"`

View File

@@ -44,6 +44,8 @@ const (
PcapKubeconfig = "kubeconfig"
PcapDumpEnabled = "enabled"
PcapTime = "time"
WatchdogEnabled = "watchdogEnabled"
HelmChartPathLabel = "release-helmChartPath"
)
type ResourceLimitsHub struct {
@@ -111,12 +113,48 @@ type DockerConfig struct {
OverrideTag OverrideTagConfig `yaml:"overrideTag" json:"overrideTag"`
}
type DnsConfig struct {
Nameservers []string `yaml:"nameservers" json:"nameservers" default:"[]"`
Searches []string `yaml:"searches" json:"searches" default:"[]"`
Options []DnsConfigOption `yaml:"options" json:"options" default:"[]"`
}
type DnsConfigOption struct {
Name string `yaml:"name" json:"name"`
Value string `yaml:"value" json:"value"`
}
type ResourcesConfig struct {
Hub ResourceRequirementsHub `yaml:"hub" json:"hub"`
Sniffer ResourceRequirementsWorker `yaml:"sniffer" json:"sniffer"`
Tracer ResourceRequirementsWorker `yaml:"tracer" json:"tracer"`
}
type ProbesConfig struct {
Hub ProbeConfig `yaml:"hub" json:"hub"`
Sniffer ProbeConfig `yaml:"sniffer" json:"sniffer"`
}
type NodeSelectorTermsConfig struct {
Hub []v1.NodeSelectorTerm `yaml:"hub" json:"hub" default:"[]"`
Workers []v1.NodeSelectorTerm `yaml:"workers" json:"workers" default:"[]"`
Front []v1.NodeSelectorTerm `yaml:"front" json:"front" default:"[]"`
Dex []v1.NodeSelectorTerm `yaml:"dex" json:"dex" default:"[]"`
}
type TolerationsConfig struct {
Hub []v1.Toleration `yaml:"hub" json:"hub" default:"[]"`
Workers []v1.Toleration `yaml:"workers" json:"workers" default:"[]"`
Front []v1.Toleration `yaml:"front" json:"front" default:"[]"`
}
type ProbeConfig struct {
InitialDelaySeconds int `yaml:"initialDelaySeconds" json:"initialDelaySeconds" default:"5"`
PeriodSeconds int `yaml:"periodSeconds" json:"periodSeconds" default:"5"`
SuccessThreshold int `yaml:"successThreshold" json:"successThreshold" default:"1"`
FailureThreshold int `yaml:"failureThreshold" json:"failureThreshold" default:"3"`
}
type ScriptingPermissions struct {
CanSave bool `yaml:"canSave" json:"canSave" default:"true"`
CanActivate bool `yaml:"canActivate" json:"canActivate" default:"true"`
@@ -130,35 +168,65 @@ type Role struct {
ScriptingPermissions ScriptingPermissions `yaml:"scriptingPermissions" json:"scriptingPermissions"`
CanUpdateTargetedPods bool `yaml:"canUpdateTargetedPods" json:"canUpdateTargetedPods" default:"false"`
CanStopTrafficCapturing bool `yaml:"canStopTrafficCapturing" json:"canStopTrafficCapturing" default:"false"`
CanControlDissection bool `yaml:"canControlDissection" json:"canControlDissection" default:"false"`
ShowAdminConsoleLink bool `yaml:"showAdminConsoleLink" json:"showAdminConsoleLink" default:"false"`
}
type SamlConfig struct {
IdpMetadataUrl string `yaml:"idpMetadataUrl" json:"idpMetadataUrl"`
X509crt string `yaml:"x509crt" json:"x509crt"`
X509key string `yaml:"x509key" json:"x509key"`
RoleAttribute string `yaml:"roleAttribute" json:"roleAttribute"`
Roles map[string]Role `yaml:"roles" json:"roles"`
IdpMetadataUrl string `yaml:"idpMetadataUrl" json:"idpMetadataUrl"`
X509crt string `yaml:"x509crt" json:"x509crt"`
X509key string `yaml:"x509key" json:"x509key"`
}
type AuthConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
Type string `yaml:"type" json:"type" default:"saml"`
Saml SamlConfig `yaml:"saml" json:"saml"`
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
// Type selects the authentication backend. Valid values:
// saml — SAML 2.0 SSO
// oidc — generic OIDC (Dex, Okta, Auth0, Keycloak, Azure AD, …)
// dex — permanent alias of oidc (kept for back-compat)
// descope — Descope SDK
// default — also routes to Descope (kept, not deprecated)
//
// NOTE: prior releases routed `oidc` to Descope. If you were using `oidc`
// to mean Descope, switch to `descope` (or `default`). The rename is a
// breaking change documented in the release notes.
Type string `yaml:"type" json:"type" default:"saml"`
Roles map[string]Role `yaml:"roles" json:"roles"`
RolesClaim string `yaml:"rolesClaim" json:"rolesClaim"`
DefaultRole string `yaml:"defaultRole" json:"defaultRole"`
DefaultFilter string `yaml:"defaultFilter" json:"defaultFilter"`
Saml SamlConfig `yaml:"saml" json:"saml"`
}
type IngressConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
ClassName string `yaml:"className" json:"className" default:""`
Host string `yaml:"host" json:"host" default:"ks.svc.cluster.local"`
Path string `yaml:"path" json:"path" default:"/"`
TLS []networking.IngressTLS `yaml:"tls" json:"tls" default:"[]"`
Annotations map[string]string `yaml:"annotations" json:"annotations" default:"{}"`
}
type RoutingConfig struct {
Front FrontRoutingConfig `yaml:"front" json:"front"`
}
type DashboardConfig struct {
StreamingType string `yaml:"streamingType" json:"streamingType" default:"connect-rpc"`
CompleteStreamingEnabled bool `yaml:"completeStreamingEnabled" json:"completeStreamingEnabled" default:"true"`
ClusterWideMapEnabled bool `yaml:"clusterWideMapEnabled" json:"clusterWideMapEnabled" default:"false"`
EntriesLimit string `yaml:"entriesLimit" json:"entriesLimit" default:"300000"`
}
type FrontRoutingConfig struct {
BasePath string `yaml:"basePath" json:"basePath" default:""`
}
type ReleaseConfig struct {
Repo string `yaml:"repo" json:"repo" default:"https://helm.kubeshark.co"`
Name string `yaml:"name" json:"name" default:"kubeshark"`
Namespace string `yaml:"namespace" json:"namespace" default:"default"`
Repo string `yaml:"repo" json:"repo" default:"https://helm.kubeshark.com"`
Name string `yaml:"name" json:"name" default:"kubeshark"`
Namespace string `yaml:"namespace" json:"namespace" default:"default"`
HelmChartPath string `yaml:"helmChartPath" json:"helmChartPath" default:""`
}
type TelemetryConfig struct {
@@ -174,6 +242,14 @@ type SentryConfig struct {
Environment string `yaml:"environment" json:"environment" default:"production"`
}
type WatchdogConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
}
type GitopsConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
}
type CapabilitiesConfig struct {
NetworkCapture []string `yaml:"networkCapture" json:"networkCapture" default:"[]"`
ServiceMeshCapture []string `yaml:"serviceMeshCapture" json:"serviceMeshCapture" default:"[]"`
@@ -192,8 +268,8 @@ type PprofConfig struct {
type MiscConfig struct {
JsonTTL string `yaml:"jsonTTL" json:"jsonTTL" default:"5m"`
PcapTTL string `yaml:"pcapTTL" json:"pcapTTL" default:"10s"`
PcapErrorTTL string `yaml:"pcapErrorTTL" json:"pcapErrorTTL" default:"60s"`
PcapTTL string `yaml:"pcapTTL" json:"pcapTTL" default:"0"`
PcapErrorTTL string `yaml:"pcapErrorTTL" json:"pcapErrorTTL" default:"0"`
TrafficSampleRate int `yaml:"trafficSampleRate" json:"trafficSampleRate" default:"100"`
TcpStreamChannelTimeoutMs int `yaml:"tcpStreamChannelTimeoutMs" json:"tcpStreamChannelTimeoutMs" default:"10000"`
TcpStreamChannelTimeoutShow bool `yaml:"tcpStreamChannelTimeoutShow" json:"tcpStreamChannelTimeoutShow" default:"false"`
@@ -201,61 +277,169 @@ type MiscConfig struct {
DuplicateTimeframe string `yaml:"duplicateTimeframe" json:"duplicateTimeframe" default:"200ms"`
DetectDuplicates bool `yaml:"detectDuplicates" json:"detectDuplicates" default:"false"`
StaleTimeoutSeconds int `yaml:"staleTimeoutSeconds" json:"staleTimeoutSeconds" default:"30"`
TcpFlowTimeout int `yaml:"tcpFlowTimeout" json:"tcpFlowTimeout" default:"1200"`
UdpFlowTimeout int `yaml:"udpFlowTimeout" json:"udpFlowTimeout" default:"1200"`
}
type PcapDumpConfig struct {
PcapDumpEnabled bool `yaml:"enabled" json:"enabled" default:"true"`
PcapDumpEnabled bool `yaml:"enabled" json:"enabled" default:"false"`
PcapTimeInterval string `yaml:"timeInterval" json:"timeInterval" default:"1m"`
PcapMaxTime string `yaml:"maxTime" json:"maxTime" default:"1h"`
PcapMaxSize string `yaml:"maxSize" json:"maxSize" default:"500MB"`
PcapSrcDir string `yaml:"pcapSrcDir" json:"pcapSrcDir" default:"pcapdump"`
PcapTime string `yaml:"time" json:"time" default:"time"`
PcapDebug bool `yaml:"debug" json:"debug" default:"false"`
PcapDest string `yaml:"dest" json:"dest" default:""`
}
type PortMapping struct {
HTTP []uint16 `yaml:"http" json:"http"`
AMQP []uint16 `yaml:"amqp" json:"amqp"`
KAFKA []uint16 `yaml:"kafka" json:"kafka"`
MONGODB []uint16 `yaml:"mongodb" json:"mongodb"`
MYSQL []uint16 `yaml:"mysql" json:"mysql"`
POSTGRESQL []uint16 `yaml:"postgresql" json:"postgresql"`
REDIS []uint16 `yaml:"redis" json:"redis"`
LDAP []uint16 `yaml:"ldap" json:"ldap"`
DIAMETER []uint16 `yaml:"diameter" json:"diameter"`
}
type SecurityContextConfig struct {
Privileged bool `yaml:"privileged" json:"privileged" default:"true"`
AppArmorProfile AppArmorProfileConfig `yaml:"appArmorProfile" json:"appArmorProfile"`
SeLinuxOptions SeLinuxOptionsConfig `yaml:"seLinuxOptions" json:"seLinuxOptions"`
Capabilities CapabilitiesConfig `yaml:"capabilities" json:"capabilities"`
}
type AppArmorProfileConfig struct {
Type string `yaml:"type" json:"type"`
LocalhostProfile string `yaml:"localhostProfile" json:"localhostProfile"`
}
type SeLinuxOptionsConfig struct {
Level string `yaml:"level" json:"level"`
Role string `yaml:"role" json:"role"`
Type string `yaml:"type" json:"type"`
User string `yaml:"user" json:"user"`
}
type RawCaptureConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"true"`
StorageSize string `yaml:"storageSize" json:"storageSize" default:"1Gi"`
}
type SnapshotsLocalConfig struct {
StorageClass string `yaml:"storageClass" json:"storageClass" default:""`
StorageSize string `yaml:"storageSize" json:"storageSize" default:"20Gi"`
}
type SnapshotsCloudS3Config struct {
Bucket string `yaml:"bucket" json:"bucket" default:""`
Region string `yaml:"region" json:"region" default:""`
AccessKey string `yaml:"accessKey" json:"accessKey" default:""`
SecretKey string `yaml:"secretKey" json:"secretKey" default:""`
RoleArn string `yaml:"roleArn" json:"roleArn" default:""`
ExternalId string `yaml:"externalId" json:"externalId" default:""`
}
type SnapshotsCloudAzblobConfig struct {
StorageAccount string `yaml:"storageAccount" json:"storageAccount" default:""`
Container string `yaml:"container" json:"container" default:""`
StorageKey string `yaml:"storageKey" json:"storageKey" default:""`
}
type SnapshotsCloudGCSConfig struct {
Bucket string `yaml:"bucket" json:"bucket" default:""`
Project string `yaml:"project" json:"project" default:""`
CredentialsJson string `yaml:"credentialsJson" json:"credentialsJson" default:""`
}
type SnapshotsCloudConfig struct {
Provider string `yaml:"provider" json:"provider" default:""`
Prefix string `yaml:"prefix" json:"prefix" default:""`
ConfigMaps []string `yaml:"configMaps" json:"configMaps" default:"[]"`
Secrets []string `yaml:"secrets" json:"secrets" default:"[]"`
S3 SnapshotsCloudS3Config `yaml:"s3" json:"s3"`
Azblob SnapshotsCloudAzblobConfig `yaml:"azblob" json:"azblob"`
GCS SnapshotsCloudGCSConfig `yaml:"gcs" json:"gcs"`
}
type SnapshotsConfig struct {
Local SnapshotsLocalConfig `yaml:"local" json:"local"`
Cloud SnapshotsCloudConfig `yaml:"cloud" json:"cloud"`
}
type DelayedDissectionConfig struct {
CPU string `yaml:"cpu" json:"cpu" default:"1"`
Memory string `yaml:"memory" json:"memory" default:"4Gi"`
StorageSize string `yaml:"storageSize" json:"storageSize" default:""`
StorageClass string `yaml:"storageClass" json:"storageClass" default:""`
}
type DissectionConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"true"`
StopAfter string `yaml:"stopAfter" json:"stopAfter" default:"5m"`
}
type CaptureConfig struct {
Dissection DissectionConfig `yaml:"dissection" json:"dissection"`
CaptureSelf bool `yaml:"captureSelf" json:"captureSelf" default:"false"`
Raw RawCaptureConfig `yaml:"raw" json:"raw"`
DbMaxSize string `yaml:"dbMaxSize" json:"dbMaxSize" default:"500Mi"`
}
type TapConfig struct {
Docker DockerConfig `yaml:"docker" json:"docker"`
Proxy ProxyConfig `yaml:"proxy" json:"proxy"`
PodRegexStr string `yaml:"regex" json:"regex" default:".*"`
Namespaces []string `yaml:"namespaces" json:"namespaces" default:"[]"`
ExcludedNamespaces []string `yaml:"excludedNamespaces" json:"excludedNamespaces" default:"[]"`
BpfOverride string `yaml:"bpfOverride" json:"bpfOverride" default:""`
Stopped bool `yaml:"stopped" json:"stopped" default:"false"`
Release ReleaseConfig `yaml:"release" json:"release"`
PersistentStorage bool `yaml:"persistentStorage" json:"persistentStorage" default:"false"`
PersistentStorageStatic bool `yaml:"persistentStorageStatic" json:"persistentStorageStatic" default:"false"`
EfsFileSytemIdAndPath string `yaml:"efsFileSytemIdAndPath" json:"efsFileSytemIdAndPath" default:""`
StorageLimit string `yaml:"storageLimit" json:"storageLimit" default:"5000Mi"`
StorageClass string `yaml:"storageClass" json:"storageClass" default:"standard"`
DryRun bool `yaml:"dryRun" json:"dryRun" default:"false"`
Resources ResourcesConfig `yaml:"resources" json:"resources"`
ServiceMesh bool `yaml:"serviceMesh" json:"serviceMesh" default:"true"`
Tls bool `yaml:"tls" json:"tls" default:"true"`
DisableTlsLog bool `yaml:"disableTlsLog" json:"disableTlsLog" default:"true"`
PacketCapture string `yaml:"packetCapture" json:"packetCapture" default:"best"`
IgnoreTainted bool `yaml:"ignoreTainted" json:"ignoreTainted" default:"false"`
Labels map[string]string `yaml:"labels" json:"labels" default:"{}"`
Annotations map[string]string `yaml:"annotations" json:"annotations" default:"{}"`
NodeSelectorTerms []v1.NodeSelectorTerm `yaml:"nodeSelectorTerms" json:"nodeSelectorTerms" default:"[]"`
Auth AuthConfig `yaml:"auth" json:"auth"`
Ingress IngressConfig `yaml:"ingress" json:"ingress"`
IPv6 bool `yaml:"ipv6" json:"ipv6" default:"true"`
Debug bool `yaml:"debug" json:"debug" default:"false"`
Telemetry TelemetryConfig `yaml:"telemetry" json:"telemetry"`
ResourceGuard ResourceGuardConfig `yaml:"resourceGuard" json:"resourceGuard"`
Sentry SentryConfig `yaml:"sentry" json:"sentry"`
DefaultFilter string `yaml:"defaultFilter" json:"defaultFilter" default:"!dns and !error"`
ScriptingDisabled bool `yaml:"scriptingDisabled" json:"scriptingDisabled" default:"false"`
TargetedPodsUpdateDisabled bool `yaml:"targetedPodsUpdateDisabled" json:"targetedPodsUpdateDisabled" default:"false"`
PresetFiltersChangingEnabled bool `yaml:"presetFiltersChangingEnabled" json:"presetFiltersChangingEnabled" default:"true"`
RecordingDisabled bool `yaml:"recordingDisabled" json:"recordingDisabled" default:"false"`
StopTrafficCapturingDisabled bool `yaml:"stopTrafficCapturingDisabled" json:"stopTrafficCapturingDisabled" default:"false"`
Capabilities CapabilitiesConfig `yaml:"capabilities" json:"capabilities"`
GlobalFilter string `yaml:"globalFilter" json:"globalFilter" default:""`
EnabledDissectors []string `yaml:"enabledDissectors" json:"enabledDissectors"`
CustomMacros map[string]string `yaml:"customMacros" json:"customMacros" default:"{\"https\":\"tls and (http or http2)\"}"`
Metrics MetricsConfig `yaml:"metrics" json:"metrics"`
Pprof PprofConfig `yaml:"pprof" json:"pprof"`
Misc MiscConfig `yaml:"misc" json:"misc"`
Docker DockerConfig `yaml:"docker" json:"docker"`
Proxy ProxyConfig `yaml:"proxy" json:"proxy"`
PodRegexStr string `yaml:"regex" json:"regex" default:".*"`
Namespaces []string `yaml:"namespaces" json:"namespaces" default:"[]"`
ExcludedNamespaces []string `yaml:"excludedNamespaces" json:"excludedNamespaces" default:"[]"`
BpfOverride string `yaml:"bpfOverride" json:"bpfOverride" default:""`
Capture CaptureConfig `yaml:"capture" json:"capture"`
DelayedDissection DelayedDissectionConfig `yaml:"delayedDissection" json:"delayedDissection"`
Snapshots SnapshotsConfig `yaml:"snapshots" json:"snapshots"`
Release ReleaseConfig `yaml:"release" json:"release"`
PersistentStorage bool `yaml:"persistentStorage" json:"persistentStorage" default:"false"`
PersistentStorageStatic bool `yaml:"persistentStorageStatic" json:"persistentStorageStatic" default:"false"`
PersistentStoragePvcVolumeMode string `yaml:"persistentStoragePvcVolumeMode" json:"persistentStoragePvcVolumeMode" default:"FileSystem"`
EfsFileSytemIdAndPath string `yaml:"efsFileSytemIdAndPath" json:"efsFileSytemIdAndPath" default:""`
Secrets []string `yaml:"secrets" json:"secrets" default:"[]"`
StorageLimit string `yaml:"storageLimit" json:"storageLimit" default:"10Gi"`
StorageClass string `yaml:"storageClass" json:"storageClass" default:"standard"`
DryRun bool `yaml:"dryRun" json:"dryRun" default:"false"`
DnsConfig DnsConfig `yaml:"dns" json:"dns"`
Resources ResourcesConfig `yaml:"resources" json:"resources"`
Probes ProbesConfig `yaml:"probes" json:"probes"`
ServiceMesh bool `yaml:"serviceMesh" json:"serviceMesh" default:"true"`
Tls bool `yaml:"tls" json:"tls" default:"true"`
DisableTlsLog bool `yaml:"disableTlsLog" json:"disableTlsLog" default:"true"`
PacketCapture string `yaml:"packetCapture" json:"packetCapture" default:"best"`
Labels map[string]string `yaml:"labels" json:"labels" default:"{}"`
Annotations map[string]string `yaml:"annotations" json:"annotations" default:"{}"`
NodeSelectorTerms NodeSelectorTermsConfig `yaml:"nodeSelectorTerms" json:"nodeSelectorTerms" default:"{}"`
Tolerations TolerationsConfig `yaml:"tolerations" json:"tolerations" default:"{}"`
Auth AuthConfig `yaml:"auth" json:"auth"`
Ingress IngressConfig `yaml:"ingress" json:"ingress"`
PriorityClass string `yaml:"priorityClass" json:"priorityClass" default:""`
Routing RoutingConfig `yaml:"routing" json:"routing"`
IPv6 bool `yaml:"ipv6" json:"ipv6" default:"true"`
Debug bool `yaml:"debug" json:"debug" default:"false"`
Dashboard DashboardConfig `yaml:"dashboard" json:"dashboard"`
Telemetry TelemetryConfig `yaml:"telemetry" json:"telemetry"`
ResourceGuard ResourceGuardConfig `yaml:"resourceGuard" json:"resourceGuard"`
Watchdog WatchdogConfig `yaml:"watchdog" json:"watchdog"`
Gitops GitopsConfig `yaml:"gitops" json:"gitops"`
Sentry SentryConfig `yaml:"sentry" json:"sentry"`
DefaultFilter string `yaml:"defaultFilter" json:"defaultFilter" default:""`
GlobalFilter string `yaml:"globalFilter" json:"globalFilter" default:""`
EnabledDissectors []string `yaml:"enabledDissectors" json:"enabledDissectors"`
PortMapping PortMapping `yaml:"portMapping" json:"portMapping"`
CustomMacros map[string]string `yaml:"customMacros" json:"customMacros" default:"{\"https\":\"tls and (http or http2)\"}"`
Metrics MetricsConfig `yaml:"metrics" json:"metrics"`
Pprof PprofConfig `yaml:"pprof" json:"pprof"`
Misc MiscConfig `yaml:"misc" json:"misc"`
SecurityContext SecurityContextConfig `yaml:"securityContext" json:"securityContext"`
MountBpf bool `yaml:"mountBpf" json:"mountBpf" default:"true"`
HostNetwork bool `yaml:"hostNetwork" json:"hostNetwork" default:"true"`
}
func (config *TapConfig) PodRegex() *regexp.Regexp {

View File

@@ -12,7 +12,7 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
)
// formatError wraps error with a detailed message that is meant for the user.
// FormatError wraps error with a detailed message that is meant for the user.
// While the errors are meant to be displayed, they are not meant to be exported as classes outsite of CLI.
func FormatError(err error) error {
var errorNew error

168
go.mod
View File

@@ -1,161 +1,143 @@
module github.com/kubeshark/kubeshark
go 1.21.1
go 1.24.0
toolchain go1.24.5
require (
github.com/creasty/defaults v1.5.2
github.com/fsnotify/fsnotify v1.6.0
github.com/fsnotify/fsnotify v1.7.0
github.com/go-cmd/cmd v1.4.3
github.com/goccy/go-yaml v1.11.2
github.com/google/go-github/v37 v37.0.0
github.com/gorilla/websocket v1.4.2
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/kubeshark/gopacket v1.1.39
github.com/pkg/errors v0.9.1
github.com/rivo/tview v0.0.0-20240818110301-fd649dbf1223
github.com/robertkrimen/otto v0.2.1
github.com/rs/zerolog v1.28.0
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/tanqiangyes/grep-go v0.0.0-20220515134556-b36bff9c3d8e
helm.sh/helm/v3 v3.12.0
k8s.io/api v0.28.3
k8s.io/apimachinery v0.28.3
k8s.io/client-go v0.28.3
k8s.io/kubectl v0.28.3
helm.sh/helm/v3 v3.18.4
k8s.io/api v0.33.2
k8s.io/apimachinery v0.33.2
k8s.io/client-go v0.33.2
k8s.io/kubectl v0.33.2
)
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/containerd v1.7.0 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.21+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v20.10.24+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.7.1 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.0.5 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.9 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/kubeshark/tracerproto v1.0.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rubenv/sql-migrate v1.3.1 // indirect
github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/stretchr/testify v1.8.3 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
go.opentelemetry.io/otel v1.14.0 // indirect
go.opentelemetry.io/otel/trace v1.14.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/term v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.26.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect
google.golang.org/grpc v1.68.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.27.1 // indirect
k8s.io/apiserver v0.27.1 // indirect
k8s.io/cli-runtime v0.28.3 // indirect
k8s.io/component-base v0.28.3 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
oras.land/oras-go v1.2.2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/apiextensions-apiserver v0.33.2 // indirect
k8s.io/apiserver v0.33.2 // indirect
k8s.io/cli-runtime v0.33.2 // indirect
k8s.io/component-base v0.33.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

1194
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
apiVersion: v2
name: kubeshark
version: "52.3.94"
version: "53.3.0"
description: The API Traffic Analyzer for Kubernetes
home: https://kubeshark.co
home: https://kubeshark.com
keywords:
- kubeshark
- packet capture
@@ -16,9 +16,9 @@ keywords:
- api
kubeVersion: '>= 1.16.0-0'
maintainers:
- email: info@kubeshark.co
- email: support@kubeshark.com
name: Kubeshark
url: https://kubeshark.co
url: https://kubeshark.com
sources:
- https://github.com/kubeshark/kubeshark/tree/master/helm-chart
type: application

View File

@@ -5,7 +5,7 @@
Add the Helm repo for Kubeshark:
```shell
helm repo add kubeshark https://helm.kubeshark.co
helm repo add kubeshark https://helm.kubeshark.com
```
then install Kubeshark:
@@ -69,7 +69,7 @@ When it's necessary, you can use:
--set license=YOUR_LICENSE_GOES_HERE
```
Get your license from Kubeshark's [Admin Console](https://console.kubeshark.co/).
Get your license from Kubeshark's [Admin Console](https://console.kubeshark.com/).
## Installing with Ingress (EKS) enabled
@@ -112,7 +112,7 @@ Example for overriding image names:
```yaml
docker:
overrideImage:
overrideImage:
worker: docker.io/kubeshark/worker:v52.3.87
front: docker.io/kubeshark/front:v52.3.87
hub: docker.io/kubeshark/hub:v52.3.87
@@ -120,99 +120,150 @@ Example for overriding image names:
## Configuration
| Parameter | Description | Default |
|-------------------------------------------|-----------------------------------------------|---------------------------------------------------------|
| `tap.docker.registry` | Docker registry to pull from | `docker.io/kubeshark` |
| `tap.docker.tag` | Tag of the Docker images | `latest` |
| `tap.docker.tagLocked` | Lock the Docker image tags to prevent automatic upgrades to the latest branch image version. | `true` |
| `tap.docker.tagLocked` | If `false` - use latest minor tag | `true` |
| `tap.docker.imagePullPolicy` | Kubernetes image pull policy | `Always` |
| `tap.docker.imagePullSecrets` | Kubernetes secrets to pull the images | `[]` |
| `tap.docker.overrideImage` | Can be used to directly override image names | `""` |
| `tap.docker.overrideTag` | Can be used to override image tags | `""` |
| `tap.proxy.hub.srvPort` | Hub server port. Change if already occupied. | `8898` |
| `tap.proxy.worker.srvPort` | Worker server port. Change if already occupied.| `48999` |
| `tap.proxy.front.port` | Front service port. Change if already occupied.| `8899` |
| `tap.proxy.host` | Change to 0.0.0.0 top open up to the world. | `127.0.0.1` |
| `tap.regex` | Target (process traffic from) pods that match regex | `.*` |
| `tap.namespaces` | Target pods in namespaces | `[]` |
| `tap.excludedNamespaces` | Exclude pods in namespaces | `[]` |
| `tap.bpfOverride` | When using AF_PACKET as a traffic capture backend, override any existing pod targeting rules and set explicit BPF expression (e.g. `net 0.0.0.0/0`). | `[]` |
| `tap.stopped` | Set to `false` to have traffic processing start automatically. When set to `true`, traffic processing is stopped by default, resulting in almost no resource consumption (e.g. Kubeshark is dormant). This property can be dynamically control via the dashboard. | `false` |
| `tap.release.repo` | URL of the Helm chart repository | `https://helm.kubeshark.co` |
| `tap.release.name` | Helm release name | `kubeshark` |
| `tap.release.namespace` | Helm release namespace | `default` |
| `tap.persistentStorage` | Use `persistentVolumeClaim` instead of `emptyDir` | `false` |
| `tap.persistentStorageStatic` | Use static persistent volume provisioning (explicitly defined `PersistentVolume` ) | `false` |
| `tap.efsFileSytemIdAndPath` | [EFS file system ID and, optionally, subpath and/or access point](https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/examples/kubernetes/access_points/README.md) `<FileSystemId>:<Path>:<AccessPointId>` | "" |
| `tap.storageLimit` | Limit of either the `emptyDir` or `persistentVolumeClaim` | `500Mi` |
| `tap.storageClass` | Storage class of the `PersistentVolumeClaim` | `standard` |
| `tap.dryRun` | Preview of all pods matching the regex, without tapping them | `false` |
| `tap.resources.hub.limits.cpu` | CPU limit for hub | `""` (no limit) |
| `tap.resources.hub.limits.memory` | Memory limit for hub | `5Gi` |
| `tap.resources.hub.requests.cpu` | CPU request for hub | `50m` |
| `tap.resources.hub.requests.memory` | Memory request for hub | `50Mi` |
| `tap.resources.sniffer.limits.cpu` | CPU limit for sniffer | `""` (no limit) |
| `tap.resources.sniffer.limits.memory` | Memory limit for sniffer | `3Gi` |
| `tap.resources.sniffer.requests.cpu` | CPU request for sniffer | `50m` |
| `tap.resources.sniffer.requests.memory` | Memory request for sniffer | `50Mi` |
| `tap.resources.tracer.limits.cpu` | CPU limit for tracer | `""` (no limit) |
| `tap.resources.tracer.limits.memory` | Memory limit for tracer | `3Gi` |
| `tap.resources.tracer.requests.cpu` | CPU request for tracer | `50m` |
| `tap.resources.tracer.requests.memory` | Memory request for tracer | `50Mi` |
| `tap.serviceMesh` | Capture traffic from service meshes like Istio, Linkerd, Consul, etc. | `true` |
| `tap.tls` | Capture the encrypted/TLS traffic from cryptography libraries like OpenSSL | `true` |
| `tap.disableTlsLog` | Suppress logging for TLS/eBPF | `true` |
| `tap.ignoreTainted` | Whether to ignore tainted nodes | `false` |
| `tap.labels` | Kubernetes labels to apply to all Kubeshark resources | `{}` |
| `tap.annotations` | Kubernetes annotations to apply to all Kubeshark resources | `{}` |
| `tap.nodeSelectorTerms` | Node selector terms | `[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]}]` |
| `tap.auth.enabled` | Enable authentication | `false` |
| `tap.auth.type` | Authentication type (1 option available: `saml`) | `saml` |
| `tap.auth.approvedEmails` | List of approved email addresses for authentication | `[]` |
| `tap.auth.approvedDomains` | List of approved email domains for authentication | `[]` |
| `tap.auth.saml.idpMetadataUrl` | SAML IDP metadata URL <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509crt` | A self-signed X.509 `.cert` contents <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509key` | A self-signed X.509 `.key` contents <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.roleAttribute` | A SAML attribute name corresponding to user's authorization role <br/>(effective, if `tap.auth.type = saml`) | `role` |
| `tap.auth.saml.roles` | A list of SAML authorization roles and their permissions <br/>(effective, if `tap.auth.type = saml`) | `{"admin":{"canDownloadPCAP":true,"canUpdateTargetedPods":true,"canUseScripting":true, "scriptingPermissions":{"canSave":true, "canActivate":true, "canDelete":true}, "canStopTrafficCapturing":true, "filter":"","showAdminConsoleLink":true}}` |
| `tap.ingress.enabled` | Enable `Ingress` | `false` |
| `tap.ingress.className` | Ingress class name | `""` |
| `tap.ingress.host` | Host of the `Ingress` | `ks.svc.cluster.local` |
| `tap.ingress.tls` | `Ingress` TLS configuration | `[]` |
| `tap.ingress.annotations` | `Ingress` annotations | `{}` |
| `tap.ipv6` | Enable IPv6 support for the front-end | `true` |
| `tap.debug` | Enable debug mode | `false` |
| `tap.telemetry.enabled` | Enable anonymous usage statistics collection | `true` |
| `tap.resourceGuard.enabled` | Enable resource guard worker process, which watches RAM/disk usage and enables/disables traffic capture based on available resources | `false` |
| `tap.sentry.enabled` | Enable sending of error logs to Sentry | `false` |
| `tap.sentry.environment` | Sentry environment to label error logs with | `production` |
| `tap.defaultFilter` | Sets the default dashboard KFL filter (e.g. `http`). By default, this value is set to filter out noisy protocols such as DNS, UDP, ICMP and TCP. The user can easily change this, **temporarily**, in the Dashboard. For a permanent change, you should change this value in the `values.yaml` or `config.yaml` file. | `"!dns and !error"` |
| `tap.globalFilter` | Prepends to any KFL filter and can be used to limit what is visible in the dashboard. For example, `redact("request.headers.Authorization")` will redact the appropriate field. Another example `!dns` will not show any DNS traffic. | `""` |
| `tap.metrics.port` | Pod port used to expose Prometheus metrics | `49100` |
| `tap.enabledDissectors` | This is an array of strings representing the list of supported protocols. Remove or comment out redundant protocols (e.g., dns).| The default list excludes: `udp` and `tcp` |
| `logs.file` | Logs dump path | `""` |
| `pcapdump.enabled` | Enable recording of all traffic captured according to other parameters. Whatever Kubeshark captures, considering pod targeting rules, will be stored in pcap files ready to be viewed by tools | `true` |
| `pcapdump.maxTime` | The time window into the past that will be stored. Older traffic will be discarded. | `2h` |
| `pcapdump.maxSize` | The maximum storage size the PCAP files will consume. Old files that cause to surpass storage consumption will get discarded. | `500MB` |
| `kube.configPath` | Path to the `kubeconfig` file (`$HOME/.kube/config`) | `""` |
| `kube.context` | Kubernetes context to use for the deployment | `""` |
| `dumpLogs` | Enable dumping of logs | `false` |
| `headless` | Enable running in headless mode | `false` |
| `license` | License key for the Pro/Enterprise edition | `""` |
| `scripting.env` | Environment variables for the scripting | `{}` |
| `scripting.source` | Source directory of the scripts | `""` |
| `scripting.watchScripts` | Enable watch mode for the scripts in source directory | `true` |
| `timezone` | IANA time zone applied to time shown in the front-end | `""` (local time zone applies) |
| `supportChatEnabled` | Enable real-time support chat channel based on Intercom | `true` |
| `internetConnectivity` | Turns off API requests that are dependant on Internet connectivity such as `telemetry` and `online-support`. | `true` |
| `dissectorsUpdatingEnabled` | Turns off UI for enabling/disabling dissectors | `true` |
| Parameter | Description | Default |
|-------------------------------------------|-----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `tap.docker.registry` | Docker registry to pull from | `docker.io/kubeshark` |
| `tap.docker.tag` | Tag of the Docker images | `latest` |
| `tap.docker.tagLocked` | Lock the Docker image tags to prevent automatic upgrades to the latest branch image version. | `true` |
| `tap.docker.tagLocked` | If `false` - use latest minor tag | `true` |
| `tap.docker.imagePullPolicy` | Kubernetes image pull policy | `Always` |
| `tap.docker.imagePullSecrets` | Kubernetes secrets to pull the images | `[]` |
| `tap.docker.overrideImage` | Can be used to directly override image names | `""` |
| `tap.docker.overrideTag` | Can be used to override image tags | `""` |
| `tap.proxy.hub.srvPort` | Hub server port. Change if already occupied. | `8898` |
| `tap.proxy.worker.srvPort` | Worker server port. Change if already occupied.| `48999` |
| `tap.proxy.front.port` | Front service port. Change if already occupied.| `8899` |
| `tap.proxy.host` | Change to 0.0.0.0 top open up to the world. | `127.0.0.1` |
| `tap.regex` | Target (process traffic from) pods that match regex | `.*` |
| `tap.namespaces` | Target pods in namespaces | `[]` |
| `tap.excludedNamespaces` | Exclude pods in namespaces | `[]` |
| `tap.bpfOverride` | When using AF_PACKET as a traffic capture backend, override any existing pod targeting rules and set explicit BPF expression (e.g. `net 0.0.0.0/0`). | `[]` |
| `tap.capture.dissection.enabled` | Set to `true` to have L7 protocol dissection start automatically. When set to `false`, dissection is disabled by default. This property can be dynamically controlled via the dashboard. | `true` |
| `tap.capture.dissection.stopAfter` | Set to a duration (e.g. `30s`) to have L7 dissection stop after no activity. | `5m` |
| `tap.capture.raw.enabled` | Enable raw capture of packets and syscalls to disk for offline analysis | `true` |
| `tap.capture.raw.storageSize` | Maximum storage size for raw capture files (supports K8s quantity format: `1Gi`, `500Mi`, etc.) | `1Gi` |
| `tap.capture.captureSelf` | Include Kubeshark's own traffic in capture | `false` |
| `tap.capture.dbMaxSize` | Maximum size for capture database (e.g., `4Gi`, `2000Mi`). | `500Mi` |
| `tap.snapshots.local.storageClass` | Storage class for local snapshots volume. When empty, uses `emptyDir`. When set, creates a PVC with this storage class | `""` |
| `tap.snapshots.local.storageSize` | Storage size for local snapshots volume (supports K8s quantity format: `1Gi`, `500Mi`, etc.) | `20Gi` |
| `tap.snapshots.cloud.provider` | Cloud storage provider for snapshots: `s3`, `azblob`, or `gcs`. Empty string disables cloud storage. See [Cloud Storage docs](docs/snapshots_cloud_storage.md). | `""` |
| `tap.snapshots.cloud.prefix` | Key prefix in the bucket/container (e.g. `snapshots/`). See [Cloud Storage docs](docs/snapshots_cloud_storage.md). | `""` |
| `tap.snapshots.cloud.configMaps` | Names of pre-existing ConfigMaps with cloud storage env vars. Alternative to inline `s3`/`azblob`/`gcs` values below. See [Cloud Storage docs](docs/snapshots_cloud_storage.md). | `[]` |
| `tap.snapshots.cloud.secrets` | Names of pre-existing Secrets with cloud storage credentials. Alternative to inline `s3`/`azblob`/`gcs` values below. See [Cloud Storage docs](docs/snapshots_cloud_storage.md). | `[]` |
| `tap.snapshots.cloud.s3.bucket` | S3 bucket name. When set, the chart auto-creates a ConfigMap with `SNAPSHOT_AWS_BUCKET`. | `""` |
| `tap.snapshots.cloud.s3.region` | AWS region for the S3 bucket. | `""` |
| `tap.snapshots.cloud.s3.accessKey` | AWS access key ID. When set, the chart auto-creates a Secret with `SNAPSHOT_AWS_ACCESS_KEY`. | `""` |
| `tap.snapshots.cloud.s3.secretKey` | AWS secret access key. When set, the chart auto-creates a Secret with `SNAPSHOT_AWS_SECRET_KEY`. | `""` |
| `tap.snapshots.cloud.s3.roleArn` | IAM role ARN to assume via STS for cross-account S3 access. | `""` |
| `tap.snapshots.cloud.s3.externalId` | External ID for the STS AssumeRole call. | `""` |
| `tap.snapshots.cloud.azblob.storageAccount` | Azure storage account name. When set, the chart auto-creates a ConfigMap with `SNAPSHOT_AZBLOB_STORAGE_ACCOUNT`. | `""` |
| `tap.snapshots.cloud.azblob.container` | Azure blob container name. | `""` |
| `tap.snapshots.cloud.azblob.storageKey` | Azure storage account access key. When set, the chart auto-creates a Secret with `SNAPSHOT_AZBLOB_STORAGE_KEY`. | `""` |
| `tap.snapshots.cloud.gcs.bucket` | GCS bucket name. When set, the chart auto-creates a ConfigMap with `SNAPSHOT_GCS_BUCKET`. | `""` |
| `tap.snapshots.cloud.gcs.project` | GCP project ID. | `""` |
| `tap.snapshots.cloud.gcs.credentialsJson` | Service account JSON key. When set, the chart auto-creates a Secret with `SNAPSHOT_GCS_CREDENTIALS_JSON`. | `""` |
| `tap.delayedDissection.cpu` | CPU allocation for delayed dissection jobs | `1` |
| `tap.delayedDissection.memory` | Memory allocation for delayed dissection jobs | `4Gi` |
| `tap.delayedDissection.storageSize` | Storage size for dissection job PVC. When empty, falls back to `tap.snapshots.local.storageSize`. When the resolved value is non-empty, a PVC is created; otherwise an `emptyDir` is used. | `""` |
| `tap.delayedDissection.storageClass` | Storage class for dissection job PVC. When empty, falls back to `tap.snapshots.local.storageClass`. | `""` |
| `tap.release.repo` | URL of the Helm chart repository | `https://helm.kubeshark.com` |
| `tap.release.name` | Helm release name | `kubeshark` |
| `tap.release.namespace` | Helm release namespace | `default` |
| `tap.persistentStorage` | Use `persistentVolumeClaim` instead of `emptyDir` | `false` |
| `tap.persistentStorageStatic` | Use static persistent volume provisioning (explicitly defined `PersistentVolume` ) | `false` |
| `tap.persistentStoragePvcVolumeMode` | Set the pvc volume mode (Filesystem\|Block) | `Filesystem` |
| `tap.efsFileSytemIdAndPath` | [EFS file system ID and, optionally, subpath and/or access point](https://github.com/kubernetes-sigs/aws-efs-csi-driver/blob/master/examples/kubernetes/access_points/README.md) `<FileSystemId>:<Path>:<AccessPointId>` | "" |
| `tap.storageLimit` | Limit of either the `emptyDir` or `persistentVolumeClaim` | `10Gi` |
| `tap.storageClass` | Storage class of the `PersistentVolumeClaim` | `standard` |
| `tap.dryRun` | Preview of all pods matching the regex, without tapping them | `false` |
| `tap.dns.nameservers` | Nameservers to use for DNS resolution | `[]` |
| `tap.dns.searches` | Search domains to use for DNS resolution | `[]` |
| `tap.dns.options` | DNS options to use for DNS resolution | `[]` |
| `tap.resources.hub.limits.cpu` | CPU limit for hub | `""` (no limit) |
| `tap.resources.hub.limits.memory` | Memory limit for hub | `5Gi` |
| `tap.resources.hub.requests.cpu` | CPU request for hub | `50m` |
| `tap.resources.hub.requests.memory` | Memory request for hub | `50Mi` |
| `tap.resources.sniffer.limits.cpu` | CPU limit for sniffer | `""` (no limit) |
| `tap.resources.sniffer.limits.memory` | Memory limit for sniffer | `5Gi` |
| `tap.resources.sniffer.requests.cpu` | CPU request for sniffer | `50m` |
| `tap.resources.sniffer.requests.memory` | Memory request for sniffer | `50Mi` |
| `tap.resources.tracer.limits.cpu` | CPU limit for tracer | `""` (no limit) |
| `tap.resources.tracer.limits.memory` | Memory limit for tracer | `5Gi` |
| `tap.resources.tracer.requests.cpu` | CPU request for tracer | `50m` |
| `tap.resources.tracer.requests.memory` | Memory request for tracer | `50Mi` |
| `tap.probes.hub.initialDelaySeconds` | Initial delay before probing the hub | `5` |
| `tap.probes.hub.periodSeconds` | Period between probes for the hub | `5` |
| `tap.probes.hub.successThreshold` | Number of successful probes before considering the hub healthy | `1` |
| `tap.probes.hub.failureThreshold` | Number of failed probes before considering the hub unhealthy | `3` |
| `tap.probes.sniffer.initialDelaySeconds` | Initial delay before probing the sniffer | `5` |
| `tap.probes.sniffer.periodSeconds` | Period between probes for the sniffer | `5` |
| `tap.probes.sniffer.successThreshold` | Number of successful probes before considering the sniffer healthy | `1` |
| `tap.probes.sniffer.failureThreshold` | Number of failed probes before considering the sniffer unhealthy | `3` |
| `tap.serviceMesh` | Capture traffic from service meshes like Istio, Linkerd, Consul, etc. | `true` |
| `tap.tls` | Capture the encrypted/TLS traffic from cryptography libraries like OpenSSL | `true` |
| `tap.disableTlsLog` | Suppress logging for TLS/eBPF | `true` |
| `tap.labels` | Kubernetes labels to apply to all Kubeshark resources | `{}` |
| `tap.annotations` | Kubernetes annotations to apply to all Kubeshark resources | `{}` |
| `tap.nodeSelectorTerms.workers` | Node selector terms for workers components | `[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]}]` |
| `tap.nodeSelectorTerms.hub` | Node selector terms for hub component | `[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]}]` |
| `tap.nodeSelectorTerms.front` | Node selector terms for front-end component | `[{"matchExpressions":[{"key":"kubernetes.io/os","operator":"In","values":["linux"]}]}]` |
| `tap.priorityClass` | Priority class name for Kubeshark components | `""` |
| `tap.tolerations.workers` | Tolerations for workers components | `[ {"operator": "Exists", "effect": "NoExecute"}` |
| `tap.tolerations.hub` | Tolerations for hub component | `[]` |
| `tap.tolerations.front` | Tolerations for front-end component | `[]` |
| `tap.auth.enabled` | Enable authentication | `false` |
| `tap.auth.type` | Authentication backend. Valid values: `saml`, `oidc` (generic OIDC — Dex, Okta, Auth0, Keycloak, Azure AD, Google, …), `dex` (permanent alias of `oidc`), `descope`, `default` (also routes to Descope). **Breaking**: prior releases routed `oidc` to Descope — if you were using it for Descope, switch to `descope` or `default`. | `saml` |
| `tap.auth.approvedEmails` | List of approved email addresses for authentication | `[]` |
| `tap.auth.approvedDomains` | List of approved email domains for authentication | `[]` |
| `tap.auth.rolesClaim` | Name of the JWT claim (OIDC) or SAML attribute carrying role memberships. | `role` |
| `tap.auth.defaultRole` | Optional role name inside `tap.auth.roles` applied as fallback when an authenticated user has no matching role. Empty string = no fallback, zero-valued permissions. | `""` |
| `tap.auth.roles` | Backend-neutral role map shared by SAML and OIDC. Each role's `namespaces` is a comma-separated list controlling which Kubernetes namespaces the role's users see traffic for: `""` = deny all, `"*"` = allow all, `"foo"` = literal namespace, `"foo,bar"` = OR over literals, `"foo-*"` = glob expansion against the cluster's known namespaces. Empty/unset `tap.auth.roles` grants nothing — admins opt into elevated access by populating this map. | `{"admin":{"namespaces":"*","canDownloadPCAP":true,"canUpdateTargetedPods":true,"canUseScripting":true,"scriptingPermissions":{"canSave":true,"canActivate":true,"canDelete":true},"canStopTrafficCapturing":true,"canControlDissection":true,"showAdminConsoleLink":true}}` |
| `tap.auth.saml.idpMetadataUrl` | SAML IDP metadata URL <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509crt` | A self-signed X.509 `.cert` contents <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509key` | A self-signed X.509 `.key` contents <br/>(effective, if `tap.auth.type = saml`) | `` |
| `tap.ingress.enabled` | Enable `Ingress` | `false` |
| `tap.ingress.className` | Ingress class name | `""` |
| `tap.ingress.host` | Host of the `Ingress` | `ks.svc.cluster.local` |
| `tap.ingress.tls` | `Ingress` TLS configuration | `[]` |
| `tap.ingress.annotations` | `Ingress` annotations | `{}` |
| `tap.routing.front.basePath` | Set this value to serve `front` under specific base path. Example: `/custompath` (forward slash must be present) | `""` |
| `tap.ipv6` | Enable IPv6 support for the front-end | `true` |
| `tap.debug` | Enable debug mode | `false` |
| `tap.telemetry.enabled` | Enable anonymous usage statistics collection | `true` |
| `tap.resourceGuard.enabled` | Enable resource guard worker process, which watches RAM/disk usage and enables/disables traffic capture based on available resources | `false` |
| `tap.secrets` | List of secrets to be used as source for environment variables (e.g. `kubeshark-license`) | `[]` |
| `tap.sentry.enabled` | Enable sending of error logs to Sentry | `false` |
| `tap.sentry.environment` | Sentry environment to label error logs with | `production` |
| `tap.defaultFilter` | Sets the default dashboard KFL filter (e.g. `http`). By default, this value is set to filter out noisy protocols such as DNS, UDP, ICMP and TCP. The user can easily change this, **temporarily**, in the Dashboard. For a permanent change, you should change this value in the `values.yaml` or `config.yaml` file. | `""` |
| `tap.globalFilter` | Prepends to any KFL filter and can be used to limit what is visible in the dashboard. For example, `redact("request.headers.Authorization")` will redact the appropriate field. Another example `!dns` will not show any DNS traffic. | `""` |
| `tap.metrics.port` | Pod port used to expose Prometheus metrics | `49100` |
| `tap.enabledDissectors` | This is an array of strings representing the list of supported protocols. Remove or comment out redundant protocols (e.g., dns).| The default list excludes: `udp` and `tcp` |
| `tap.mountBpf` | BPF filesystem needs to be mounted for eBPF to work properly. This helm value determines whether Kubeshark will attempt to mount the filesystem. This option is not required if filesystem is already mounts. │ `true`|
| `tap.hostNetwork` | Enable host network mode for worker DaemonSet pods. When enabled, worker pods use the host's network namespace for direct network access. | `true` |
| `tap.packetCapture` | Packet capture backend: `best`, `af_packet`, or `pf_ring` | `best` |
| `tap.misc.trafficSampleRate` | Percentage of traffic to process (0-100) | `100` |
| `tap.misc.tcpStreamChannelTimeoutMs` | Timeout in milliseconds for TCP stream channel | `10000` |
| `tap.gitops.enabled` | Enable GitOps functionality. This will allow you to use GitOps to manage your Kubeshark configuration. | `false` |
| `tap.misc.tcpFlowTimeout` | TCP flow aggregation timeout in seconds. Controls how long the worker waits before finalizing a TCP flow. | `1200` |
| `tap.misc.udpFlowTimeout` | UDP flow aggregation timeout in seconds. Controls how long the worker waits before finalizing a UDP flow. | `1200` |
| `logs.file` | Logs dump path | `""` |
| `pcapdump.enabled` | Enable recording of all traffic captured according to other parameters. Whatever Kubeshark captures, considering pod targeting rules, will be stored in pcap files ready to be viewed by tools | `false` |
| `pcapdump.maxTime` | The time window into the past that will be stored. Older traffic will be discarded. | `2h` |
| `pcapdump.maxSize` | The maximum storage size the PCAP files will consume. Old files that cause to surpass storage consumption will get discarded. | `500MB` |
| `kube.configPath` | Path to the `kubeconfig` file (`$HOME/.kube/config`) | `""` |
| `kube.context` | Kubernetes context to use for the deployment | `""` |
| `dumpLogs` | Enable dumping of logs | `false` |
| `headless` | Enable running in headless mode | `false` |
| `license` | License key for the Pro/Enterprise edition | `""` |
| `scripting.enabled` | Enables scripting | `false` |
| `scripting.env` | Environment variables for the scripting | `{}` |
| `scripting.source` | Source directory of the scripts | `""` |
| `scripting.watchScripts` | Enable watch mode for the scripts in source directory | `true` |
| `timezone` | IANA time zone applied to time shown in the front-end | `""` (local time zone applies) |
| `supportChatEnabled` | Enable real-time support chat channel based on Intercom | `false` |
| `internetConnectivity` | Turns off API requests that are dependent on Internet connectivity such as `telemetry` and `online-support`. | `true` |
KernelMapping pairs kernel versions with a
DriverContainer image. Kernel versions can be matched
literally or using a regular expression
## Installing with SAML enabled
# Installing with SAML enabled
### Prerequisites:
@@ -277,3 +328,239 @@ tap:
UaV5sbRtTzYLxpOSQyi8CEFA+A==
-----END PRIVATE KEY-----
```
# Installing with Dex OIDC authentication
[**Click here to see full docs**](https://docs.kubeshark.com/en/saml#installing-with-oidc-enabled-dex-idp).
Choose this option, if **you already have a running instance** of Dex in your cluster &
you want to set up Dex OIDC authentication for Kubeshark users.
Kubeshark supports authentication using [Dex - A Federated OpenID Connect Provider](https://dexidp.io/).
Dex is an abstraction layer designed for integrating a wide variety of Identity Providers.
**Requirement:**
Your Dex IdP must have a publicly accessible URL.
### Pre-requisites:
**1. If you configured Ingress for Kubeshark:**
(see section: "Installing with Ingress (EKS) enabled")
OAuth2 callback URL is: <br/>
`https://<kubeshark-ingress-hostname>/api/oauth2/callback`
**2. If you did not configure Ingress for Kubeshark:**
OAuth2 callback URL is: <br/>
`http://0.0.0.0:8899/api/oauth2/callback`
Use chosen OAuth2 callback URL to replace `<your-kubeshark-host>` in Step 3.
**3. Add this static client to your Dex IdP configuration (`config.yaml`):**
```yaml
staticClients:
- id: kubeshark
secret: create your own client password
name: Kubeshark
redirectURIs:
- https://<your-kubeshark-host>/api/oauth2/callback
```
**Final step:**
Add these helm values to set up OIDC authentication powered by your Dex IdP:
```yaml
# values.yaml
tap:
auth:
enabled: true
type: oidc # canonical; `dex` is accepted as a permanent alias
oidc:
issuer: <put Dex IdP issuer URL here>
clientId: kubeshark
clientSecret: create your own client password
refreshTokenLifetime: "3960h" # 165 days
oauth2StateParamExpiry: "10m"
bypassSslCaCheck: false
```
---
**Note:**<br/>
Set `tap.auth.oidc.bypassSslCaCheck: true`
to allow Kubeshark communication with Dex IdP having an unknown SSL Certificate Authority.
This setting allows you to prevent such SSL CA-related errors:<br/>
`tls: failed to verify certificate: x509: certificate signed by unknown authority`
---
Once you run `helm install kubeshark kubeshark/kubeshark -f ./values.yaml`, Kubeshark will be installed with (Dex) OIDC authentication enabled.
---
# Installing your own Dex IdP along with Kubeshark
Choose this option, if **you need to deploy an instance of Dex IdP** along with Kubeshark &
set up Dex OIDC authentication for Kubeshark users.
Depending on Ingress enabled/disabled, your Dex configuration might differ.
**Requirement:**
Please, configure Ingress using `tap.ingress` for your Kubeshark installation. For example:
```yaml
tap:
ingress:
enabled: true
className: "alb"
host: ks.example.com
tls: []
annotations:
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:7..8:certificate/b...65c
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/scheme: internet-facing
```
The following Dex settings will have these values:
| Setting | Value |
|-------------------------------------------------------|----------------------------------------------|
| `tap.auth.oidc.issuer` | `https://ks.example.com/dex` |
| `tap.auth.dexConfig.issuer` | `https://ks.example.com/dex` |
| `tap.auth.dexConfig.staticClients -> redirectURIs` | `https://ks.example.com/api/oauth2/callback` |
| `tap.auth.dexConfig.connectors -> config.redirectURI` | `https://ks.example.com/dex/callback` |
---
### Before proceeding with Dex IdP installation:
Please, make sure to prepare the following things first.
1. Choose **[Connectors](https://dexidp.io/docs/connectors/)** to enable in Dex IdP.
- i.e. how many kind of "Log in with ..." options you'd like to offer your users
- You will need to specify connectors in `tap.auth.dexConfig.connectors`
2. Choose type of **[Storage](https://dexidp.io/docs/configuration/storage/)** to use in Dex IdP.
- You will need to specify storage settings in `tap.auth.dexConfig.storage`
- default: `memory`
3. Decide on the OAuth2 `?state=` param expiration time:
- field: `tap.auth.oidc.oauth2StateParamExpiry`
- default: `10m` (10 minutes)
- valid time units are `s`, `m`, `h`
4. Decide on the refresh token expiration:
- field 1: `tap.auth.oidc.expiry.refreshTokenLifetime`
- field 2: `tap.auth.dexConfig.expiry.refreshTokens.absoluteLifetime`
- default: `3960h` (165 days)
- valid time units are `s`, `m`, `h`
5. Create a unique & secure password to set in these fields:
- field 1: `tap.auth.oidc.clientSecret`
- field 2: `tap.auth.dexConfig.staticClients -> secret`
- password must be the same for these 2 fields
6. Discover more possibilities of **[Dex Configuration](https://dexidp.io/docs/configuration/)**
- if you decide to include more configuration options, make sure to add them into `tap.auth.dexConfig`
---
### Once you are ready with all the points described above:
Use these helm `values.yaml` fields to:
- Deploy your own instance of Dex IdP along with Kubeshark
- Enable OIDC authentication for Kubeshark users
Make sure to:
- Replace `<your-ingress-hostname>` with a correct Kubeshark Ingress host (`tap.auth.ingress.host`).
- refer to section **Installing with Ingress (EKS) enabled** to find out how you can configure Ingress host.
Helm `values.yaml`:
```yaml
tap:
auth:
enabled: true
type: oidc # canonical; `dex` is accepted as a permanent alias
oidc:
issuer: https://<your-ingress-hostname>/dex
# Client ID/secret must be taken from `tap.auth.dexConfig.staticClients -> id/secret`
clientId: kubeshark
clientSecret: create your own client password
refreshTokenLifetime: "3960h" # 165 days
oauth2StateParamExpiry: "10m"
bypassSslCaCheck: false
dexConfig:
# This field is REQUIRED!
#
# The base path of Dex and the external name of the OpenID Connect service.
# This is the canonical URL that all clients MUST use to refer to Dex. If a
# path is provided, Dex's HTTP service will listen at a non-root URL.
issuer: https://<your-ingress-hostname>/dex
# Expiration configuration for tokens, signing keys, etc.
expiry:
refreshTokens:
validIfNotUsedFor: "2160h" # 90 days
absoluteLifetime: "3960h" # 165 days
# This field is REQUIRED!
#
# The storage configuration determines where Dex stores its state.
# See the documentation (https://dexidp.io/docs/storage/) for further information.
storage:
type: memory
# This field is REQUIRED!
#
# Attention:
# Do not change this field and its values.
# This field is required for internal Kubeshark-to-Dex communication.
#
# HTTP service configuration
web:
http: 0.0.0.0:5556
# This field is REQUIRED!
#
# Attention:
# Do not change this field and its values.
# This field is required for internal Kubeshark-to-Dex communication.
#
# Telemetry configuration
telemetry:
http: 0.0.0.0:5558
# This field is REQUIRED!
#
# Static clients registered in Dex by default.
staticClients:
- id: kubeshark
secret: create your own client password
name: Kubeshark
redirectURIs:
- https://<your-ingress-hostname>/api/oauth2/callback
# Enable the password database.
# It's a "virtual" connector (identity provider) that stores
# login credentials in Dex's store.
enablePasswordDB: true
# Connectors are used to authenticate users against upstream identity providers.
# See the documentation (https://dexidp.io/docs/connectors/) for further information.
#
# Attention:
# When you define a new connector, `config.redirectURI` must be:
# https://<your-ingress-hostname>/dex/callback
#
# Example with Google connector:
# connectors:
# - type: google
# id: google
# name: Google
# config:
# clientID: your Google Cloud Auth app client ID
# clientSecret: your Google Auth app client ID
# redirectURI: https://<your-ingress-hostname>/dex/callback
connectors: []
```

View File

@@ -0,0 +1,583 @@
# Cloud Storage for Snapshots
Kubeshark can upload and download snapshots to cloud object storage, enabling cross-cluster sharing, backup/restore, and long-term retention.
Supported providers: **Amazon S3** (`s3`), **Azure Blob Storage** (`azblob`), and **Google Cloud Storage** (`gcs`).
## Helm Values
```yaml
tap:
snapshots:
cloud:
provider: "" # "s3", "azblob", or "gcs" (empty = disabled)
prefix: "" # key prefix in the bucket/container (e.g. "snapshots/")
configMaps: [] # names of pre-existing ConfigMaps with cloud config env vars
secrets: [] # names of pre-existing Secrets with cloud credentials
s3:
bucket: ""
region: ""
accessKey: ""
secretKey: ""
roleArn: ""
externalId: ""
azblob:
storageAccount: ""
container: ""
storageKey: ""
gcs:
bucket: ""
project: ""
credentialsJson: ""
```
- `provider` selects which cloud backend to use. Leave empty to disable cloud storage.
- `configMaps` and `secrets` are lists of names of existing ConfigMap/Secret resources. They are mounted as `envFrom` on the hub pod, injecting all their keys as environment variables.
### Inline Values (Alternative to External ConfigMaps/Secrets)
Instead of creating ConfigMap and Secret resources manually, you can set cloud storage configuration directly in `values.yaml` or via `--set` flags. The Helm chart will automatically create the necessary ConfigMap and Secret resources.
Both approaches can be used together — inline values are additive to external `configMaps`/`secrets` references.
---
## Amazon S3
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `SNAPSHOT_AWS_BUCKET` | Yes | S3 bucket name |
| `SNAPSHOT_AWS_REGION` | No | AWS region (uses SDK default if empty) |
| `SNAPSHOT_AWS_ACCESS_KEY` | No | Static access key ID (empty = use default credential chain) |
| `SNAPSHOT_AWS_SECRET_KEY` | No | Static secret access key |
| `SNAPSHOT_AWS_ROLE_ARN` | No | IAM role ARN to assume via STS (for cross-account access) |
| `SNAPSHOT_AWS_EXTERNAL_ID` | No | External ID for the STS AssumeRole call |
| `SNAPSHOT_CLOUD_PREFIX` | No | Key prefix in the bucket (e.g. `snapshots/`) |
### Authentication Methods
Credentials are resolved in this order:
1. **Static credentials** -- If `SNAPSHOT_AWS_ACCESS_KEY` is set, static credentials are used directly.
2. **STS AssumeRole** -- If `SNAPSHOT_AWS_ROLE_ARN` is also set, the static (or default) credentials are used to assume the given IAM role. This is useful for cross-account S3 access.
3. **AWS default credential chain** -- When no static credentials are provided, the SDK default chain is used:
- **IRSA** (EKS service account token) -- recommended for production on EKS
- EC2 instance profile
- Standard AWS environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, etc.)
- Shared credentials file (`~/.aws/credentials`)
The provider validates bucket access on startup via `HeadBucket`. If the bucket is inaccessible, the hub will fail to start.
### Example: Inline Values (simplest approach)
```yaml
tap:
snapshots:
cloud:
provider: "s3"
s3:
bucket: my-kubeshark-snapshots
region: us-east-1
```
Or with static credentials via `--set`:
```bash
helm install kubeshark kubeshark/kubeshark \
--set tap.snapshots.cloud.provider=s3 \
--set tap.snapshots.cloud.s3.bucket=my-kubeshark-snapshots \
--set tap.snapshots.cloud.s3.region=us-east-1 \
--set tap.snapshots.cloud.s3.accessKey=AKIA... \
--set tap.snapshots.cloud.s3.secretKey=wJal...
```
### Example: IRSA (recommended for EKS)
[IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) lets EKS pods assume an IAM role without static credentials. EKS injects a short-lived token into the pod automatically.
**Prerequisites:**
1. Your EKS cluster must have an [OIDC provider](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html) associated with it.
2. An IAM role with a trust policy that allows the Kubeshark service account to assume it.
**Step 1 — Create an IAM policy scoped to your bucket:**
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:GetObjectVersion",
"s3:DeleteObjectVersion",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:GetBucketLocation",
"s3:GetBucketVersioning"
],
"Resource": [
"arn:aws:s3:::my-kubeshark-snapshots",
"arn:aws:s3:::my-kubeshark-snapshots/*"
]
}
]
}
```
> For read-only access, remove `s3:PutObject`, `s3:DeleteObject`, and `s3:DeleteObjectVersion`.
**Step 2 — Create an IAM role with IRSA trust policy:**
```bash
# Get your cluster's OIDC provider URL
OIDC_PROVIDER=$(aws eks describe-cluster --name CLUSTER_NAME \
--query "cluster.identity.oidc.issuer" --output text | sed 's|https://||')
# Create a trust policy
# The default K8s SA name is "<release-name>-service-account" (e.g. "kubeshark-service-account")
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/${OIDC_PROVIDER}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OIDC_PROVIDER}:sub": "system:serviceaccount:NAMESPACE:kubeshark-service-account",
"${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
# Create the role and attach your policy
aws iam create-role \
--role-name KubesharkS3Role \
--assume-role-policy-document file://trust-policy.json
aws iam put-role-policy \
--role-name KubesharkS3Role \
--policy-name KubesharkSnapshotsBucketAccess \
--policy-document file://bucket-policy.json
```
**Step 3 — Create a ConfigMap with bucket configuration:**
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-s3-config
data:
SNAPSHOT_AWS_BUCKET: my-kubeshark-snapshots
SNAPSHOT_AWS_REGION: us-east-1
```
**Step 4 — Set Helm values with `tap.annotations` to annotate the service account:**
```yaml
tap:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/KubesharkS3Role
snapshots:
cloud:
provider: "s3"
configMaps:
- kubeshark-s3-config
```
Or via `--set`:
```bash
helm install kubeshark kubeshark/kubeshark \
--set tap.snapshots.cloud.provider=s3 \
--set tap.snapshots.cloud.s3.bucket=my-kubeshark-snapshots \
--set tap.snapshots.cloud.s3.region=us-east-1 \
--set tap.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::ACCOUNT_ID:role/KubesharkS3Role
```
No `accessKey`/`secretKey` is needed — EKS injects credentials automatically via the IRSA token.
### Example: Static Credentials
Create a Secret with credentials:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: kubeshark-s3-creds
type: Opaque
stringData:
SNAPSHOT_AWS_ACCESS_KEY: AKIA...
SNAPSHOT_AWS_SECRET_KEY: wJal...
```
Create a ConfigMap with bucket configuration:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-s3-config
data:
SNAPSHOT_AWS_BUCKET: my-kubeshark-snapshots
SNAPSHOT_AWS_REGION: us-east-1
```
Set Helm values:
```yaml
tap:
snapshots:
cloud:
provider: "s3"
configMaps:
- kubeshark-s3-config
secrets:
- kubeshark-s3-creds
```
### Example: Cross-Account Access via AssumeRole
Add the role ARN to your ConfigMap:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-s3-config
data:
SNAPSHOT_AWS_BUCKET: other-account-bucket
SNAPSHOT_AWS_REGION: eu-west-1
SNAPSHOT_AWS_ROLE_ARN: arn:aws:iam::123456789012:role/KubesharkCrossAccountRole
SNAPSHOT_AWS_EXTERNAL_ID: my-external-id # optional, if required by the trust policy
```
The hub will first authenticate using its own credentials (IRSA, static, or default chain), then assume the specified role to access the bucket.
---
## Azure Blob Storage
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `SNAPSHOT_AZBLOB_STORAGE_ACCOUNT` | Yes | Azure storage account name |
| `SNAPSHOT_AZBLOB_CONTAINER` | Yes | Blob container name |
| `SNAPSHOT_AZBLOB_STORAGE_KEY` | No | Storage account access key (empty = use DefaultAzureCredential) |
| `SNAPSHOT_CLOUD_PREFIX` | No | Key prefix in the container (e.g. `snapshots/`) |
### Authentication Methods
Credentials are resolved in this order:
1. **Shared Key** -- If `SNAPSHOT_AZBLOB_STORAGE_KEY` is set, the storage account key is used directly.
2. **DefaultAzureCredential** -- When no storage key is provided, the Azure SDK default credential chain is used:
- **Workload Identity** (AKS pod identity) -- recommended for production on AKS
- Managed Identity (system or user-assigned)
- Azure CLI credentials
- Environment variables (`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET`)
The provider validates container access on startup via `GetProperties`. If the container is inaccessible, the hub will fail to start.
### Example: Inline Values
```yaml
tap:
snapshots:
cloud:
provider: "azblob"
azblob:
storageAccount: mykubesharksa
container: snapshots
storageKey: "base64-encoded-storage-key..." # optional, omit for DefaultAzureCredential
```
### Example: Workload Identity (recommended for AKS)
Create a ConfigMap with storage configuration:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-azblob-config
data:
SNAPSHOT_AZBLOB_STORAGE_ACCOUNT: mykubesharksa
SNAPSHOT_AZBLOB_CONTAINER: snapshots
```
Set Helm values:
```yaml
tap:
snapshots:
cloud:
provider: "azblob"
configMaps:
- kubeshark-azblob-config
```
The hub pod's service account must be configured for AKS Workload Identity with a managed identity that has the **Storage Blob Data Contributor** role on the container.
### Example: Storage Account Key
Create a Secret with the storage key:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: kubeshark-azblob-creds
type: Opaque
stringData:
SNAPSHOT_AZBLOB_STORAGE_KEY: "base64-encoded-storage-key..."
```
Create a ConfigMap with storage configuration:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-azblob-config
data:
SNAPSHOT_AZBLOB_STORAGE_ACCOUNT: mykubesharksa
SNAPSHOT_AZBLOB_CONTAINER: snapshots
```
Set Helm values:
```yaml
tap:
snapshots:
cloud:
provider: "azblob"
configMaps:
- kubeshark-azblob-config
secrets:
- kubeshark-azblob-creds
```
---
## Google Cloud Storage
### Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `SNAPSHOT_GCS_BUCKET` | Yes | GCS bucket name |
| `SNAPSHOT_GCS_PROJECT` | No | GCP project ID |
| `SNAPSHOT_GCS_CREDENTIALS_JSON` | No | Service account JSON key (empty = use Application Default Credentials) |
| `SNAPSHOT_CLOUD_PREFIX` | No | Key prefix in the bucket (e.g. `snapshots/`) |
### Authentication Methods
Credentials are resolved in this order:
1. **Service Account JSON Key** -- If `SNAPSHOT_GCS_CREDENTIALS_JSON` is set, the provided JSON key is used directly.
2. **Application Default Credentials** -- When no JSON key is provided, the GCP SDK default credential chain is used:
- **Workload Identity** (GKE pod identity) -- recommended for production on GKE
- GCE instance metadata (Compute Engine default service account)
- Standard GCP environment variables (`GOOGLE_APPLICATION_CREDENTIALS`)
- `gcloud` CLI credentials
The provider validates bucket access on startup via `Bucket.Attrs()`. If the bucket is inaccessible, the hub will fail to start.
### Required IAM Permissions
The service account needs different IAM roles depending on the access level:
**Read-only** (download, list, and sync snapshots from cloud):
| Role | Permissions provided | Purpose |
|------|---------------------|---------|
| `roles/storage.legacyBucketReader` | `storage.buckets.get`, `storage.objects.list` | Hub startup (bucket validation) + listing snapshots |
| `roles/storage.objectViewer` | `storage.objects.get`, `storage.objects.list` | Downloading snapshots, checking existence, reading metadata |
```bash
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:SA_EMAIL" \
--role="roles/storage.legacyBucketReader"
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:SA_EMAIL" \
--role="roles/storage.objectViewer"
```
**Read-write** (upload and delete snapshots in addition to read):
Add `roles/storage.objectAdmin` instead of `roles/storage.objectViewer` to also grant `storage.objects.create` and `storage.objects.delete`:
| Role | Permissions provided | Purpose |
|------|---------------------|---------|
| `roles/storage.legacyBucketReader` | `storage.buckets.get`, `storage.objects.list` | Hub startup (bucket validation) + listing snapshots |
| `roles/storage.objectAdmin` | `storage.objects.*` | Full object CRUD (upload, download, delete, list, metadata) |
```bash
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:SA_EMAIL" \
--role="roles/storage.legacyBucketReader"
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:SA_EMAIL" \
--role="roles/storage.objectAdmin"
```
### Example: Inline Values (simplest approach)
```yaml
tap:
snapshots:
cloud:
provider: "gcs"
gcs:
bucket: my-kubeshark-snapshots
project: my-gcp-project
```
Or with a service account key via `--set`:
```bash
helm install kubeshark kubeshark/kubeshark \
--set tap.snapshots.cloud.provider=gcs \
--set tap.snapshots.cloud.gcs.bucket=my-kubeshark-snapshots \
--set tap.snapshots.cloud.gcs.project=my-gcp-project \
--set-file tap.snapshots.cloud.gcs.credentialsJson=service-account.json
```
### Example: Workload Identity (recommended for GKE)
Create a ConfigMap with bucket configuration:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-gcs-config
data:
SNAPSHOT_GCS_BUCKET: my-kubeshark-snapshots
SNAPSHOT_GCS_PROJECT: my-gcp-project
```
Set Helm values:
```yaml
tap:
snapshots:
cloud:
provider: "gcs"
configMaps:
- kubeshark-gcs-config
```
Configure GKE Workload Identity to allow the Kubernetes service account to impersonate the GCP service account:
```bash
# Ensure the GKE cluster has Workload Identity enabled
# (--workload-pool=PROJECT_ID.svc.id.goog at cluster creation)
# Create a GCP service account (if not already created)
gcloud iam service-accounts create kubeshark-gcs \
--display-name="Kubeshark GCS Snapshots"
# Grant bucket access (read-write — see Required IAM Permissions above)
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:kubeshark-gcs@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.legacyBucketReader"
gcloud storage buckets add-iam-policy-binding gs://BUCKET_NAME \
--member="serviceAccount:kubeshark-gcs@PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
# Allow the K8s service account to impersonate the GCP service account
# Note: the K8s SA name is "<release-name>-service-account" (default: "kubeshark-service-account")
gcloud iam service-accounts add-iam-policy-binding \
kubeshark-gcs@PROJECT_ID.iam.gserviceaccount.com \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/kubeshark-service-account]"
```
Set Helm values — the `tap.annotations` field adds the Workload Identity annotation to the service account:
```yaml
tap:
annotations:
iam.gke.io/gcp-service-account: kubeshark-gcs@PROJECT_ID.iam.gserviceaccount.com
snapshots:
cloud:
provider: "gcs"
configMaps:
- kubeshark-gcs-config
```
Or via `--set`:
```bash
helm install kubeshark kubeshark/kubeshark \
--set tap.snapshots.cloud.provider=gcs \
--set tap.snapshots.cloud.gcs.bucket=BUCKET_NAME \
--set tap.snapshots.cloud.gcs.project=PROJECT_ID \
--set tap.annotations."iam\.gke\.io/gcp-service-account"=kubeshark-gcs@PROJECT_ID.iam.gserviceaccount.com
```
No `credentialsJson` secret is needed — GKE injects credentials automatically via the Workload Identity metadata server.
### Example: Service Account Key
Create a Secret with the service account JSON key:
```yaml
apiVersion: v1
kind: Secret
metadata:
name: kubeshark-gcs-creds
type: Opaque
stringData:
SNAPSHOT_GCS_CREDENTIALS_JSON: |
{
"type": "service_account",
"project_id": "my-gcp-project",
"private_key_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "kubeshark@my-gcp-project.iam.gserviceaccount.com",
...
}
```
Create a ConfigMap with bucket configuration:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubeshark-gcs-config
data:
SNAPSHOT_GCS_BUCKET: my-kubeshark-snapshots
SNAPSHOT_GCS_PROJECT: my-gcp-project
```
Set Helm values:
```yaml
tap:
snapshots:
cloud:
provider: "gcs"
configMaps:
- kubeshark-gcs-config
secrets:
- kubeshark-gcs-creds
```

View File

@@ -41,6 +41,7 @@ prometheus:
| --- | --- | --- |
| kubeshark_received_packets_total | Counter | Total number of packets received |
| kubeshark_dropped_packets_total | Counter | Total number of packets dropped |
| kubeshark_dropped_chunks_total | Counter | Total number of dropped packet chunks |
| kubeshark_processed_bytes_total | Counter | Total number of bytes processed |
| kubeshark_tcp_packets_total | Counter | Total number of TCP packets |
| kubeshark_dns_packets_total | Counter | Total number of DNS packets |

View File

@@ -4,9 +4,15 @@ kind: ServiceAccount
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: {{ include "kubeshark.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- if .Values.tap.docker.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.tap.docker.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}

View File

@@ -4,8 +4,8 @@ kind: ClusterRole
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-cluster-role-{{ .Release.Namespace }}
@@ -44,6 +44,12 @@ rules:
- create
- update
- delete
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
@@ -63,12 +69,41 @@ rules:
resourceNames:
- kubeshark-secret
- kubeshark-config-map
- kubeshark-secret-default
- kubeshark-config-map-default
resources:
- secrets
- configmaps
verbs:
- create
- get
- watch
- list
- update
- patch
- delete
- apiGroups:
- ""
- v1
resources:
- secrets
- configmaps
- pods/log
verbs:
- create
- get
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- create
- get
- list
- delete
- apiGroups:
- batch
resources:
- jobs
verbs:
- "*"

View File

@@ -4,8 +4,8 @@ kind: ClusterRoleBinding
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-cluster-role-binding-{{ .Release.Namespace }}

View File

@@ -3,10 +3,10 @@ apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: {{ include "kubeshark.name" . }}-hub
@@ -15,16 +15,19 @@ spec:
replicas: 1 # Set the desired number of replicas
selector:
matchLabels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 8 }}
spec:
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: {{ include "kubeshark.serviceAccountName" . }}
{{- if .Values.tap.priorityClass }}
priorityClassName: {{ .Values.tap.priorityClass | quote }}
{{- end }}
containers:
- name: hub
command:
@@ -33,6 +36,70 @@ spec:
- "8080"
- -loglevel
- '{{ .Values.logLevel | default "warning" }}'
- -capture-stop-after
- "{{ if hasKey .Values.tap.capture.dissection "stopAfter" }}{{ .Values.tap.capture.dissection.stopAfter }}{{ else }}5m{{ end }}"
- -snapshot-size-limit
- '{{ .Values.tap.snapshots.local.storageSize }}'
- -dissector-image
{{- if .Values.tap.docker.overrideImage.worker }}
- '{{ .Values.tap.docker.overrideImage.worker }}'
{{- else if .Values.tap.docker.overrideTag.worker }}
- '{{ .Values.tap.docker.registry }}/worker:{{ .Values.tap.docker.overrideTag.worker }}'
{{- else }}
- '{{ .Values.tap.docker.registry }}/worker:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}'
{{- end }}
{{- if .Values.tap.delayedDissection.cpu }}
- -dissector-cpu
- '{{ .Values.tap.delayedDissection.cpu }}'
{{- end }}
{{- if .Values.tap.delayedDissection.memory }}
- -dissector-memory
- '{{ .Values.tap.delayedDissection.memory }}'
{{- end }}
{{- $dissectorStorageSize := .Values.tap.delayedDissection.storageSize | default .Values.tap.snapshots.local.storageSize }}
{{- if $dissectorStorageSize }}
- -dissector-storage-size
- '{{ $dissectorStorageSize }}'
{{- end }}
{{- $dissectorStorageClass := .Values.tap.delayedDissection.storageClass | default .Values.tap.snapshots.local.storageClass }}
{{- if $dissectorStorageClass }}
- -dissector-storage-class
- '{{ $dissectorStorageClass }}'
{{- end }}
{{- if .Values.tap.gitops.enabled }}
- -gitops
{{- end }}
- -cloud-api-url
- '{{ .Values.cloudApiUrl }}'
{{- if .Values.tap.snapshots.cloud.provider }}
- -cloud-storage-provider
- '{{ .Values.tap.snapshots.cloud.provider }}'
{{- end }}
{{- $hasInlineConfig := or .Values.tap.snapshots.cloud.prefix .Values.tap.snapshots.cloud.s3.bucket .Values.tap.snapshots.cloud.s3.region .Values.tap.snapshots.cloud.s3.roleArn .Values.tap.snapshots.cloud.s3.externalId .Values.tap.snapshots.cloud.azblob.storageAccount .Values.tap.snapshots.cloud.azblob.container .Values.tap.snapshots.cloud.gcs.bucket .Values.tap.snapshots.cloud.gcs.project }}
{{- $hasInlineSecrets := or .Values.tap.snapshots.cloud.s3.accessKey .Values.tap.snapshots.cloud.s3.secretKey .Values.tap.snapshots.cloud.azblob.storageKey .Values.tap.snapshots.cloud.gcs.credentialsJson }}
{{- if or .Values.tap.secrets .Values.tap.snapshots.cloud.configMaps .Values.tap.snapshots.cloud.secrets $hasInlineConfig $hasInlineSecrets }}
envFrom:
{{- range .Values.tap.secrets }}
- secretRef:
name: {{ . }}
{{- end }}
{{- range .Values.tap.snapshots.cloud.configMaps }}
- configMapRef:
name: {{ . }}
{{- end }}
{{- range .Values.tap.snapshots.cloud.secrets }}
- secretRef:
name: {{ . }}
{{- end }}
{{- if $hasInlineConfig }}
- configMapRef:
name: {{ include "kubeshark.name" . }}-cloud-config
{{- end }}
{{- if $hasInlineSecrets }}
- secretRef:
name: {{ include "kubeshark.name" . }}-cloud-secret
{{- end }}
{{- end }}
env:
- name: POD_NAME
valueFrom:
@@ -46,8 +113,6 @@ spec:
value: '{{ (include "sentry.enabled" .) }}'
- name: SENTRY_ENVIRONMENT
value: '{{ .Values.tap.sentry.environment }}'
- name: KUBESHARK_CLOUD_API_URL
value: 'https://api.kubeshark.co'
- name: PROFILING_ENABLED
value: '{{ .Values.tap.pprof.enabled }}'
{{- if .Values.tap.docker.overrideImage.hub }}
@@ -58,24 +123,18 @@ spec:
image: '{{ .Values.tap.docker.registry }}/hub:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
{{- if .Values.tap.docker.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.tap.docker.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
readinessProbe:
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
periodSeconds: {{ .Values.tap.probes.hub.periodSeconds }}
failureThreshold: {{ .Values.tap.probes.hub.failureThreshold }}
successThreshold: {{ .Values.tap.probes.hub.successThreshold }}
initialDelaySeconds: {{ .Values.tap.probes.hub.initialDelaySeconds }}
tcpSocket:
port: 8080
livenessProbe:
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
periodSeconds: {{ .Values.tap.probes.hub.periodSeconds }}
failureThreshold: {{ .Values.tap.probes.hub.failureThreshold }}
successThreshold: {{ .Values.tap.probes.hub.successThreshold }}
initialDelaySeconds: {{ .Values.tap.probes.hub.initialDelaySeconds }}
tcpSocket:
port: 8080
resources:
@@ -90,13 +149,62 @@ spec:
{{ if ne (toString .Values.tap.resources.hub.requests.cpu) "0" }}
cpu: {{ .Values.tap.resources.hub.requests.cpu }}
{{ end }}
{{ if ne (toString .Values.tap.resources.hub.requests.memor) "0" }}
{{ if ne (toString .Values.tap.resources.hub.requests.memory) "0" }}
memory: {{ .Values.tap.resources.hub.requests.memory }}
{{ end }}
volumeMounts:
- name: saml-x509-volume
mountPath: "/etc/saml/x509"
readOnly: true
- name: snapshots-volume
mountPath: "/app/data/snapshots"
{{- if gt (len .Values.tap.nodeSelectorTerms.hub) 0}}
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
{{- toYaml .Values.tap.nodeSelectorTerms.hub | nindent 12 }}
{{- end }}
{{- if or .Values.tap.dns.nameservers .Values.tap.dns.searches .Values.tap.dns.options }}
dnsConfig:
{{- if .Values.tap.dns.nameservers }}
nameservers:
{{- range .Values.tap.dns.nameservers }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.searches }}
searches:
{{- range .Values.tap.dns.searches }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.options }}
options:
{{- range .Values.tap.dns.options }}
- name: {{ .name | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.tap.tolerations.hub }}
tolerations:
{{- range .Values.tap.tolerations.hub }}
- key: {{ .key | quote }}
operator: {{ .operator | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- if .effect }}
effect: {{ .effect | quote }}
{{- end }}
{{- if .tolerationSeconds }}
tolerationSeconds: {{ .tolerationSeconds }}
{{- end }}
{{- end }}
{{- end }}
volumes:
- name: saml-x509-volume
projected:
@@ -111,3 +219,11 @@ spec:
items:
- key: AUTH_SAML_X509_KEY
path: kubeshark.key
- name: snapshots-volume
{{- if .Values.tap.snapshots.local.storageClass }}
persistentVolumeClaim:
claimName: {{ include "kubeshark.name" . }}-snapshots-pvc
{{- else }}
emptyDir:
sizeLimit: {{ .Values.tap.snapshots.local.storageSize }}
{{- end }}

View File

@@ -3,10 +3,10 @@ apiVersion: v1
kind: Service
metadata:
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-hub
@@ -17,5 +17,5 @@ spec:
port: 80
targetPort: 8080
selector:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
type: ClusterIP

View File

@@ -2,10 +2,10 @@ apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: {{ include "kubeshark.name" . }}-front
@@ -14,43 +14,63 @@ spec:
replicas: 1 # Set the desired number of replicas
selector:
matchLabels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
{{- include "kubeshark.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
{{- include "kubeshark.labels" . | nindent 8 }}
spec:
containers:
- env:
- name: REACT_APP_AUTH_ENABLED
value: '{{- if or (and .Values.cloudLicenseEnabled (not (empty .Values.license))) (not .Values.internetConnectivity) -}}
"false"
{{- else -}}
{{ .Values.cloudLicenseEnabled | ternary "true" .Values.tap.auth.enabled }}
{{- end }}'
{{ (default false .Values.demoModeEnabled) | ternary true ((and .Values.tap.auth.enabled (or (eq .Values.tap.auth.type "oidc") (eq .Values.tap.auth.type "dex"))) | ternary true false) }}
{{- else -}}
{{ .Values.cloudLicenseEnabled | ternary "true" ((default false .Values.demoModeEnabled) | ternary "true" .Values.tap.auth.enabled) }}
{{- end }}'
- name: REACT_APP_AUTH_TYPE
value: '{{ not (eq .Values.tap.auth.type "") | ternary (.Values.cloudLicenseEnabled | ternary "oidc" .Values.tap.auth.type) " " }}'
value: '{{- if and .Values.cloudLicenseEnabled (not (or (eq .Values.tap.auth.type "oidc") (eq .Values.tap.auth.type "dex"))) -}}
default
{{- else -}}
{{ (default false .Values.demoModeEnabled) | ternary "default" .Values.tap.auth.type }}
{{- end }}'
- name: REACT_APP_COMPLETE_STREAMING_ENABLED
value: '{{- if and (hasKey .Values.tap "dashboard") (hasKey .Values.tap.dashboard "completeStreamingEnabled") -}}
{{ eq .Values.tap.dashboard.completeStreamingEnabled true | ternary "true" "false" }}
{{- else -}}
true
{{- end }}'
- name: REACT_APP_STREAMING_TYPE
value: '{{ default "" (((.Values).tap).dashboard).streamingType }}'
- name: REACT_APP_AUTH_SAML_IDP_METADATA_URL
value: '{{ not (eq .Values.tap.auth.saml.idpMetadataUrl "") | ternary .Values.tap.auth.saml.idpMetadataUrl " " }}'
- name: REACT_APP_TIMEZONE
value: '{{ not (eq .Values.timezone "") | ternary .Values.timezone " " }}'
- name: REACT_APP_SCRIPTING_DISABLED
value: '{{ .Values.tap.scriptingDisabled }}'
- name: REACT_APP_TARGETED_PODS_UPDATE_DISABLED
value: '{{ .Values.tap.targetedPodsUpdateDisabled }}'
- name: REACT_APP_PRESET_FILTERS_CHANGING_ENABLED
value: '{{ .Values.tap.presetFiltersChangingEnabled }}'
- name: REACT_APP_BPF_OVERRIDE_DISABLED
value: '{{ eq .Values.tap.packetCapture "ebpf" | ternary "true" "false" }}'
- name: REACT_APP_RECORDING_DISABLED
value: '{{ .Values.tap.recordingDisabled }}'
- name: REACT_APP_STOP_TRAFFIC_CAPTURING_DISABLED
value: '{{- if and .Values.tap.stopTrafficCapturingDisabled .Values.tap.stopped -}}
false
- name: REACT_APP_SCRIPTING_HIDDEN
value: '{{- if and .Values.scripting (eq (.Values.scripting.enabled | toString) "false") -}}
true
{{- else -}}
{{ .Values.tap.stopTrafficCapturingDisabled | ternary "true" "false" }}
false
{{- end }}'
- name: REACT_APP_SCRIPTING_DISABLED
value: '{{ default false .Values.demoModeEnabled }}'
- name: REACT_APP_TARGETED_PODS_UPDATE_DISABLED
value: '{{ default false .Values.demoModeEnabled }}'
- name: REACT_APP_PRESET_FILTERS_CHANGING_ENABLED
value: '{{ not (default false .Values.demoModeEnabled) }}'
- name: REACT_APP_BPF_OVERRIDE_DISABLED
value: '{{ eq .Values.tap.packetCapture "af_packet" | ternary "false" "true" }}'
- name: REACT_APP_RECORDING_DISABLED
value: '{{ default false .Values.demoModeEnabled }}'
- name: REACT_APP_DISSECTION_ENABLED
value: '{{ .Values.tap.capture.dissection.enabled | ternary "true" "false" }}'
- name: REACT_APP_DISSECTION_CONTROL_ENABLED
value: '{{- if and (not .Values.demoModeEnabled) (not .Values.tap.capture.dissection.enabled) -}}
true
{{- else -}}
{{ (default false .Values.demoModeEnabled) | ternary false true }}
{{- end -}}'
- name: 'REACT_APP_CLOUD_LICENSE_ENABLED'
value: '{{- if or (and .Values.cloudLicenseEnabled (not (empty .Values.license))) (not .Values.internetConnectivity) -}}
@@ -60,8 +80,20 @@ spec:
{{- end }}'
- name: REACT_APP_SUPPORT_CHAT_ENABLED
value: '{{ and .Values.supportChatEnabled .Values.internetConnectivity | ternary "true" "false" }}'
- name: REACT_APP_BETA_ENABLED
value: '{{ default false .Values.betaEnabled | ternary "true" "false" }}'
- name: REACT_APP_DISSECTORS_UPDATING_ENABLED
value: '{{ .Values.dissectorsUpdatingEnabled | ternary "true" "false" }}'
value: '{{ not (default false .Values.demoModeEnabled) }}'
- name: REACT_APP_SNAPSHOTS_UPDATING_ENABLED
value: '{{ not (default false .Values.demoModeEnabled) }}'
- name: REACT_APP_DEMO_MODE_ENABLED
value: '{{ default false .Values.demoModeEnabled }}'
- name: REACT_APP_CLUSTER_WIDE_MAP_ENABLED
value: '{{ default false (((.Values).tap).dashboard).clusterWideMapEnabled }}'
- name: REACT_APP_RAW_CAPTURE_ENABLED
value: '{{ .Values.tap.capture.raw.enabled | ternary "true" "false" }}'
- name: REACT_APP_ENTRIES_LIMIT
value: '{{ default 300000 (((.Values).tap).dashboard).entriesLimit }}'
- name: REACT_APP_SENTRY_ENABLED
value: '{{ (include "sentry.enabled" .) }}'
- name: REACT_APP_SENTRY_ENVIRONMENT
@@ -74,12 +106,6 @@ spec:
image: '{{ .Values.tap.docker.registry }}/front:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
{{- if .Values.tap.docker.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.tap.docker.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
name: kubeshark-front
livenessProbe:
periodSeconds: 1
@@ -108,9 +134,59 @@ spec:
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
{{- if gt (len .Values.tap.nodeSelectorTerms.front) 0}}
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
{{- toYaml .Values.tap.nodeSelectorTerms.front | nindent 12 }}
{{- end }}
{{- if or .Values.tap.dns.nameservers .Values.tap.dns.searches .Values.tap.dns.options }}
dnsConfig:
{{- if .Values.tap.dns.nameservers }}
nameservers:
{{- range .Values.tap.dns.nameservers }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.searches }}
searches:
{{- range .Values.tap.dns.searches }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.options }}
options:
{{- range .Values.tap.dns.options }}
- name: {{ .name | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.tap.tolerations.front }}
tolerations:
{{- range .Values.tap.tolerations.front }}
- key: {{ .key | quote }}
operator: {{ .operator | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- if .effect }}
effect: {{ .effect | quote }}
{{- end }}
{{- if .tolerationSeconds }}
tolerationSeconds: {{ .tolerationSeconds }}
{{- end }}
{{- end }}
{{- end }}
volumes:
- name: nginx-config
configMap:
name: kubeshark-nginx-config-map
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: {{ include "kubeshark.serviceAccountName" . }}
{{- if .Values.tap.priorityClass }}
priorityClassName: {{ .Values.tap.priorityClass | quote }}
{{- end }}

View File

@@ -4,8 +4,8 @@ kind: Service
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-front
@@ -16,5 +16,5 @@ spec:
port: 80
targetPort: 8080
selector:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
type: ClusterIP

View File

@@ -26,13 +26,14 @@ kind: PersistentVolumeClaim
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-persistent-volume-claim
namespace: {{ .Release.Namespace }}
spec:
volumeMode: {{ .Values.tap.persistentStoragePvcVolumeMode }}
accessModes:
- ReadWriteMany
resources:

View File

@@ -0,0 +1,22 @@
---
{{- if .Values.tap.snapshots.local.storageClass }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: {{ include "kubeshark.name" . }}-snapshots-pvc
namespace: {{ .Release.Namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.tap.snapshots.local.storageSize }}
storageClassName: {{ .Values.tap.snapshots.local.storageClass }}
status: {}
{{- end }}

View File

@@ -3,11 +3,11 @@ apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
sidecar.istio.io/inject: "false"
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-worker-daemon-set
@@ -15,49 +15,60 @@ metadata:
spec:
selector:
matchLabels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
{{- include "kubeshark.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
kubeshark.io/internal-auth: "true"
{{- include "kubeshark.labels" . | nindent 8 }}
name: kubeshark-worker-daemon-set
namespace: kubeshark
spec:
{{- if or .Values.tap.mountBpf .Values.tap.persistentStorage}}
initContainers:
{{- end }}
{{- if .Values.tap.mountBpf }}
- command:
- /bin/sh
- -c
- mkdir -p /sys/fs/bpf && mount | grep -q '/sys/fs/bpf' || mount -t bpf bpf /sys/fs/bpf
{{- if .Values.tap.docker.overrideTag.worker }}
{{- if .Values.tap.docker.overrideTag.worker }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ .Values.tap.docker.overrideTag.worker }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{ else }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
name: check-bpf
name: mount-bpf
securityContext:
privileged: true
volumeMounts:
- mountPath: /sys
name: sys
mountPropagation: Bidirectional
{{- end }}
{{- if .Values.tap.persistentStorage }}
- command:
- ./tracer
- -init-bpf
{{- if .Values.tap.docker.overrideTag.worker }}
- /bin/sh
- -c
- mkdir -p /app/data/$NODE_NAME && rm -rf /app/data/$NODE_NAME/tracer_*
{{- if .Values.tap.docker.overrideTag.worker }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ .Values.tap.docker.overrideTag.worker }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{ else }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
name: init-bpf
securityContext:
privileged: true
name: cleanup-data-dir
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- mountPath: /sys
name: sys
- mountPath: /app/data
name: data
{{- end }}
containers:
- command:
- ./worker
@@ -71,24 +82,34 @@ spec:
- '{{ .Values.tap.packetCapture }}'
- -loglevel
- '{{ .Values.logLevel | default "warning" }}'
{{- if .Values.tap.tls }}
- -unixsocket
{{- if not .Values.tap.tls }}
- -disable-tracer
{{- end }}
{{- if .Values.tap.serviceMesh }}
- -servicemesh
{{- end }}
- -procfs
- /hostproc
{{- if ne .Values.tap.packetCapture "ebpf" }}
- -disable-ebpf
{{- end }}
{{- if .Values.tap.resourceGuard.enabled }}
- -enable-resource-guard
{{- end }}
{{- if .Values.tap.watchdog.enabled }}
- -enable-watchdog
{{- end }}
- -resolution-strategy
- '{{ .Values.tap.misc.resolutionStrategy }}'
- -staletimeout
- '{{ .Values.tap.misc.staleTimeoutSeconds }}'
- -tcp-flow-full-timeout
- '{{ .Values.tap.misc.tcpFlowTimeout }}'
- -udp-flow-full-timeout
- '{{ .Values.tap.misc.udpFlowTimeout }}'
- -storage-size
- '{{ .Values.tap.storageLimit }}'
- -capture-db-max-size
- '{{ .Values.tap.capture.dbMaxSize }}'
- -cloud-api-url
- '{{ .Values.cloudApiUrl }}'
{{- if .Values.tap.docker.overrideImage.worker }}
image: '{{ .Values.tap.docker.overrideImage.worker }}'
{{- else if .Values.tap.docker.overrideTag.worker }}
@@ -97,12 +118,6 @@ spec:
image: '{{ .Values.tap.docker.registry }}/worker:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
{{- if .Values.tap.docker.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.tap.docker.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
name: sniffer
ports:
- containerPort: {{ .Values.tap.metrics.port }}
@@ -117,12 +132,14 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: TCP_STREAM_CHANNEL_TIMEOUT_MS
value: '{{ .Values.tap.misc.tcpStreamChannelTimeoutMs }}'
- name: TCP_STREAM_CHANNEL_TIMEOUT_SHOW
value: '{{ .Values.tap.misc.tcpStreamChannelTimeoutShow }}'
- name: KUBESHARK_CLOUD_API_URL
value: 'https://api.kubeshark.co'
- name: PROFILING_ENABLED
value: '{{ .Values.tap.pprof.enabled }}'
- name: SENTRY_ENABLED
@@ -145,35 +162,64 @@ spec:
memory: {{ .Values.tap.resources.sniffer.requests.memory }}
{{ end }}
securityContext:
privileged: {{ .Values.tap.securityContext.privileged }}
{{- if not .Values.tap.securityContext.privileged }}
{{- $aaProfile := .Values.tap.securityContext.appArmorProfile }}
{{- $selinuxOpts := .Values.tap.securityContext.seLinuxOptions }}
{{- if or (ne $aaProfile.type "") (ne $aaProfile.localhostProfile "") }}
appArmorProfile:
{{- if ne $aaProfile.type "" }}
type: {{ $aaProfile.type }}
{{- end }}
{{- if ne $aaProfile.localhostProfile "" }}
localhostProfile: {{ $aaProfile.localhostProfile }}
{{- end }}
{{- end }}
{{- if or (ne $selinuxOpts.level "") (ne $selinuxOpts.role "") (ne $selinuxOpts.type "") (ne $selinuxOpts.user "") }}
seLinuxOptions:
{{- if ne $selinuxOpts.level "" }}
level: {{ $selinuxOpts.level }}
{{- end }}
{{- if ne $selinuxOpts.role "" }}
role: {{ $selinuxOpts.role }}
{{- end }}
{{- if ne $selinuxOpts.type "" }}
type: {{ $selinuxOpts.type }}
{{- end }}
{{- if ne $selinuxOpts.user "" }}
user: {{ $selinuxOpts.user }}
{{- end }}
{{- end }}
capabilities:
add:
{{- range .Values.tap.capabilities.networkCapture }}
{{- range .Values.tap.securityContext.capabilities.networkCapture }}
{{ print "- " . }}
{{- end }}
{{- if .Values.tap.serviceMesh }}
{{- range .Values.tap.capabilities.serviceMeshCapture }}
{{- range .Values.tap.securityContext.capabilities.serviceMeshCapture }}
{{ print "- " . }}
{{- end }}
{{- end }}
{{- if .Values.tap.capabilities.ebpfCapture }}
{{- range .Values.tap.capabilities.ebpfCapture }}
{{- if .Values.tap.securityContext.capabilities.ebpfCapture }}
{{- range .Values.tap.securityContext.capabilities.ebpfCapture }}
{{ print "- " . }}
{{- end }}
{{- end }}
drop:
- ALL
{{- end }}
readinessProbe:
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 5
periodSeconds: {{ .Values.tap.probes.sniffer.periodSeconds }}
failureThreshold: {{ .Values.tap.probes.sniffer.failureThreshold }}
successThreshold: {{ .Values.tap.probes.sniffer.successThreshold }}
initialDelaySeconds: {{ .Values.tap.probes.sniffer.initialDelaySeconds }}
tcpSocket:
port: {{ .Values.tap.proxy.worker.srvPort }}
livenessProbe:
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 5
periodSeconds: {{ .Values.tap.probes.sniffer.periodSeconds }}
failureThreshold: {{ .Values.tap.probes.sniffer.failureThreshold }}
successThreshold: {{ .Values.tap.probes.sniffer.successThreshold }}
initialDelaySeconds: {{ .Values.tap.probes.sniffer.initialDelaySeconds }}
tcpSocket:
port: {{ .Values.tap.proxy.worker.srvPort }}
volumeMounts:
@@ -183,16 +229,17 @@ spec:
- mountPath: /sys
name: sys
readOnly: true
mountPropagation: HostToContainer
- mountPath: /app/data
name: data
{{- if .Values.tap.persistentStorage }}
subPathExpr: $(NODE_NAME)
{{- end }}
{{- if .Values.tap.tls }}
- command:
- ./tracer
- -procfs
- /hostproc
{{- if ne .Values.tap.packetCapture "ebpf" }}
- -disable-ebpf
{{- end }}
{{- if .Values.tap.disableTlsLog }}
- -disable-tls-log
{{- end }}
@@ -200,20 +247,14 @@ spec:
- -port
- '{{ add .Values.tap.proxy.worker.srvPort 1 }}'
{{- end }}
# - -loglevel
# - '{{ .Values.logLevel | default "warning" }}'
- -loglevel
- '{{ .Values.logLevel | default "warning" }}'
{{- if .Values.tap.docker.overrideTag.worker }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ .Values.tap.docker.overrideTag.worker }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{ else }}
image: '{{ .Values.tap.docker.registry }}/worker:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}{{ include "kubeshark.dockerTagDebugVersion" . }}'
{{- end }}
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
{{- if .Values.tap.docker.imagePullSecrets }}
imagePullSecrets:
{{- range .Values.tap.docker.imagePullSecrets }}
- name: {{ . }}
{{- end }}
{{- end }}
name: tracer
env:
- name: POD_NAME
@@ -224,6 +265,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: PROFILING_ENABLED
value: '{{ .Values.tap.pprof.enabled }}'
- name: SENTRY_ENABLED
@@ -246,16 +291,45 @@ spec:
memory: {{ .Values.tap.resources.tracer.requests.memory }}
{{ end }}
securityContext:
privileged: {{ .Values.tap.securityContext.privileged }}
{{- if not .Values.tap.securityContext.privileged }}
{{- $aaProfile := .Values.tap.securityContext.appArmorProfile }}
{{- $selinuxOpts := .Values.tap.securityContext.seLinuxOptions }}
{{- if or (ne $aaProfile.type "") (ne $aaProfile.localhostProfile "") }}
appArmorProfile:
{{- if ne $aaProfile.type "" }}
type: {{ $aaProfile.type }}
{{- end }}
{{- if ne $aaProfile.localhostProfile "" }}
localhostProfile: {{ $aaProfile.localhostProfile }}
{{- end }}
{{- end }}
{{- if or (ne $selinuxOpts.level "") (ne $selinuxOpts.role "") (ne $selinuxOpts.type "") (ne $selinuxOpts.user "") }}
seLinuxOptions:
{{- if ne $selinuxOpts.level "" }}
level: {{ $selinuxOpts.level }}
{{- end }}
{{- if ne $selinuxOpts.role "" }}
role: {{ $selinuxOpts.role }}
{{- end }}
{{- if ne $selinuxOpts.type "" }}
type: {{ $selinuxOpts.type }}
{{- end }}
{{- if ne $selinuxOpts.user "" }}
user: {{ $selinuxOpts.user }}
{{- end }}
{{- end }}
capabilities:
add:
{{- range .Values.tap.capabilities.ebpfCapture }}
{{- range .Values.tap.securityContext.capabilities.ebpfCapture }}
{{ print "- " . }}
{{- end }}
{{- range .Values.tap.capabilities.networkCapture }}
{{- range .Values.tap.securityContext.capabilities.networkCapture }}
{{ print "- " . }}
{{- end }}
drop:
- ALL
{{- end }}
volumeMounts:
- mountPath: /hostproc
name: proc
@@ -263,8 +337,12 @@ spec:
- mountPath: /sys
name: sys
readOnly: true
mountPropagation: HostToContainer
- mountPath: /app/data
name: data
{{- if .Values.tap.persistentStorage }}
subPathExpr: $(NODE_NAME)
{{- end }}
- mountPath: /etc/os-release
name: os-release
readOnly: true
@@ -274,23 +352,58 @@ spec:
readOnly: true
{{- end }}
dnsPolicy: ClusterFirstWithHostNet
hostNetwork: true
hostNetwork: {{ .Values.tap.hostNetwork }}
serviceAccountName: {{ include "kubeshark.serviceAccountName" . }}
terminationGracePeriodSeconds: 0
{{- if .Values.tap.priorityClass }}
priorityClassName: {{ .Values.tap.priorityClass | quote }}
{{- end }}
{{- if .Values.tap.tolerations.workers }}
tolerations:
- effect: NoExecute
operator: Exists
{{- if not .Values.tap.ignoreTainted }}
- effect: NoSchedule
operator: Exists
{{- end }}
{{- if gt (len .Values.tap.nodeSelectorTerms) 0}}
{{- range .Values.tap.tolerations.workers }}
- key: {{ .key | quote }}
operator: {{ .operator | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- if .effect }}
effect: {{ .effect | quote }}
{{- end }}
{{- if .tolerationSeconds }}
tolerationSeconds: {{ .tolerationSeconds }}
{{- end }}
{{- end }}
{{- end }}
{{- if gt (len .Values.tap.nodeSelectorTerms.workers) 0}}
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
{{- toYaml .Values.tap.nodeSelectorTerms | nindent 12 }}
{{- toYaml .Values.tap.nodeSelectorTerms.workers | nindent 12 }}
{{- end }}
{{- if or .Values.tap.dns.nameservers .Values.tap.dns.searches .Values.tap.dns.options }}
dnsConfig:
{{- if .Values.tap.dns.nameservers }}
nameservers:
{{- range .Values.tap.dns.nameservers }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.searches }}
searches:
{{- range .Values.tap.dns.searches }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.options }}
options:
{{- range .Values.tap.dns.options }}
- name: {{ .name | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
volumes:
- hostPath:
path: /proc
@@ -304,9 +417,11 @@ spec:
- hostPath:
path: /etc/os-release
name: os-release
{{- if .Values.tap.tls }}
- hostPath:
path: /
name: root
{{- end }}
- name: data
{{- if .Values.tap.persistentStorage }}
persistentVolumeClaim:

View File

@@ -28,7 +28,7 @@ spec:
name: kubeshark-front
port:
number: 80
path: /
path: {{ default "/" (((.Values).tap).ingress).path }}
pathType: Prefix
{{- if .Values.tap.ingress.tls }}
tls:

View File

@@ -20,8 +20,12 @@ data:
client_header_buffer_size 32k;
large_client_header_buffers 8 64k;
location /api {
rewrite ^/api(.*)$ $1 break;
proxy_buffer_size 64k;
proxy_buffers 4 128k;
proxy_busy_buffers_size 128k;
location {{ default "" (((.Values.tap).routing).front).basePath }}/api {
rewrite ^{{ default "" (((.Values.tap).routing).front).basePath }}/api(.*)$ $1 break;
proxy_pass http://kubeshark-hub;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
@@ -30,21 +34,51 @@ data:
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
proxy_connect_timeout 4s;
# Disable buffering for gRPC/Connect streaming
client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;
proxy_pass_request_headers on;
}
location {{ default "" (((.Values.tap).routing).front).basePath }}/saml {
rewrite ^{{ default "" (((.Values.tap).routing).front).basePath }}/saml(.*)$ /saml$1 break;
proxy_pass http://kubeshark-hub;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_connect_timeout 4s;
proxy_read_timeout 120s;
proxy_send_timeout 12s;
proxy_pass_request_headers on;
}
{{- if .Values.tap.auth.dexConfig }}
location /dex {
rewrite ^{{ default "" (((.Values.tap).routing).front).basePath }}/dex(.*)$ /dex$1 break;
proxy_pass http://kubeshark-dex;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Upgrade websocket;
proxy_set_header Connection Upgrade;
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
proxy_connect_timeout 4s;
proxy_read_timeout 120s;
proxy_send_timeout 12s;
proxy_pass_request_headers on;
}
{{- end }}
location /saml {
rewrite ^/saml(.*)$ /saml$1 break;
proxy_pass http://kubeshark-hub;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
proxy_connect_timeout 4s;
proxy_read_timeout 120s;
proxy_send_timeout 12s;
proxy_pass_request_headers on;
{{- if (((.Values.tap).routing).front).basePath }}
location {{ .Values.tap.routing.front.basePath }} {
rewrite ^{{ .Values.tap.routing.front.basePath }}(.*)$ $1 break;
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
expires -1;
add_header Cache-Control no-cache;
}
{{- end }}
location / {
root /usr/share/nginx/html;
@@ -58,4 +92,3 @@ data:
root /usr/share/nginx/html;
}
}

View File

@@ -1,41 +1,59 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: kubeshark-config-map
name: {{ include "kubeshark.configmapName" . }}
namespace: {{ .Release.Namespace }}
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
data:
POD_REGEX: '{{ .Values.tap.regex }}'
NAMESPACES: '{{ gt (len .Values.tap.namespaces) 0 | ternary (join "," .Values.tap.namespaces) "" }}'
EXCLUDED_NAMESPACES: '{{ gt (len .Values.tap.excludedNamespaces) 0 | ternary (join "," .Values.tap.excludedNamespaces) "" }}'
BPF_OVERRIDE: '{{ .Values.tap.bpfOverride }}'
STOPPED: '{{ .Values.tap.stopped | ternary "true" "false" }}'
DISSECTION_ENABLED: '{{ .Values.tap.capture.dissection.enabled | ternary "true" "false" }}'
CAPTURE_SELF: '{{ .Values.tap.capture.captureSelf | ternary "true" "false" }}'
SCRIPTING_SCRIPTS: '{}'
SCRIPTING_ACTIVE_SCRIPTS: '{{ gt (len .Values.scripting.active) 0 | ternary (join "," .Values.scripting.active) "" }}'
INGRESS_ENABLED: '{{ .Values.tap.ingress.enabled }}'
INGRESS_HOST: '{{ .Values.tap.ingress.host }}'
PROXY_FRONT_PORT: '{{ .Values.tap.proxy.front.port }}'
AUTH_ENABLED: '{{- if and .Values.cloudLicenseEnabled (not (empty .Values.license)) -}}
"false"
{{ (default false .Values.demoModeEnabled) | ternary true ((and .Values.tap.auth.enabled (or (eq .Values.tap.auth.type "oidc") (eq .Values.tap.auth.type "dex"))) | ternary true false) }}
{{- else -}}
{{ .Values.cloudLicenseEnabled | ternary "true" (.Values.tap.auth.enabled | ternary "true" "") }}
{{ .Values.cloudLicenseEnabled | ternary "true" ((default false .Values.demoModeEnabled) | ternary "true" .Values.tap.auth.enabled) }}
{{- end }}'
AUTH_TYPE: '{{ .Values.cloudLicenseEnabled | ternary "oidc" (.Values.tap.auth.type) }}'
AUTH_TYPE: '{{- if and .Values.cloudLicenseEnabled (not (or (eq .Values.tap.auth.type "oidc") (eq .Values.tap.auth.type "dex"))) -}}
default
{{- else -}}
{{ (default false .Values.demoModeEnabled) | ternary "default" .Values.tap.auth.type }}
{{- end }}'
AUTH_SAML_IDP_METADATA_URL: '{{ .Values.tap.auth.saml.idpMetadataUrl }}'
AUTH_SAML_ROLE_ATTRIBUTE: '{{ .Values.tap.auth.saml.roleAttribute }}'
AUTH_SAML_ROLES: '{{ .Values.tap.auth.saml.roles | toJson }}'
AUTH_ROLES: '{{ .Values.tap.auth.roles | toJson }}'
AUTH_ROLES_CLAIM: '{{ .Values.tap.auth.rolesClaim }}'
AUTH_DEFAULT_ROLE: '{{ default "" .Values.tap.auth.defaultRole }}'
AUTH_OIDC_ISSUER: '{{ default "not set" (((.Values.tap).auth).oidc).issuer }}'
AUTH_OIDC_REFRESH_TOKEN_LIFETIME: '{{ default "3960h" (((.Values.tap).auth).oidc).refreshTokenLifetime }}'
AUTH_OIDC_STATE_PARAM_EXPIRY: '{{ default "10m" (((.Values.tap).auth).oidc).oauth2StateParamExpiry }}'
AUTH_OIDC_BYPASS_SSL_CA_CHECK: '{{- if and
(hasKey .Values.tap "auth")
(hasKey .Values.tap.auth "oidc")
(hasKey .Values.tap.auth.oidc "bypassSslCaCheck")
-}}
{{ eq .Values.tap.auth.oidc.bypassSslCaCheck true | ternary "true" "false" }}
{{- else -}}
false
{{- end }}'
TELEMETRY_DISABLED: '{{ not .Values.internetConnectivity | ternary "true" (not .Values.tap.telemetry.enabled | ternary "true" "false") }}'
SCRIPTING_DISABLED: '{{ .Values.tap.scriptingDisabled | ternary "true" "" }}'
TARGETED_PODS_UPDATE_DISABLED: '{{ .Values.tap.targetedPodsUpdateDisabled | ternary "true" "" }}'
PRESET_FILTERS_CHANGING_ENABLED: '{{ .Values.tap.presetFiltersChangingEnabled | ternary "true" "" }}'
RECORDING_DISABLED: '{{ .Values.tap.recordingDisabled | ternary "true" "" }}'
STOP_TRAFFIC_CAPTURING_DISABLED: '{{- if and .Values.tap.stopTrafficCapturingDisabled .Values.tap.stopped -}}
false
{{- else -}}
{{ .Values.tap.stopTrafficCapturingDisabled | ternary "true" "false" }}
{{- end }}'
SCRIPTING_DISABLED: '{{ default false .Values.demoModeEnabled }}'
TARGETED_PODS_UPDATE_DISABLED: '{{ default false .Values.demoModeEnabled }}'
PRESET_FILTERS_CHANGING_ENABLED: '{{ not (default false .Values.demoModeEnabled) }}'
RECORDING_DISABLED: '{{ (default false .Values.demoModeEnabled) | ternary true false }}'
DISSECTION_CONTROL_ENABLED: '{{- if and (not .Values.demoModeEnabled) (not .Values.tap.capture.dissection.enabled) -}}
true
{{- else -}}
{{ (default false .Values.demoModeEnabled) | ternary false true }}
{{- end }}'
GLOBAL_FILTER: {{ include "kubeshark.escapeDoubleQuotes" .Values.tap.globalFilter | quote }}
DEFAULT_FILTER: {{ include "kubeshark.escapeDoubleQuotes" .Values.tap.defaultFilter | quote }}
TRAFFIC_SAMPLE_RATE: '{{ .Values.tap.misc.trafficSampleRate }}'
@@ -51,10 +69,14 @@ data:
DUPLICATE_TIMEFRAME: '{{ .Values.tap.misc.duplicateTimeframe }}'
ENABLED_DISSECTORS: '{{ gt (len .Values.tap.enabledDissectors) 0 | ternary (join "," .Values.tap.enabledDissectors) "" }}'
CUSTOM_MACROS: '{{ toJson .Values.tap.customMacros }}'
DISSECTORS_UPDATING_ENABLED: '{{ .Values.dissectorsUpdatingEnabled | ternary "true" "false" }}'
DISSECTORS_UPDATING_ENABLED: '{{ not (default false .Values.demoModeEnabled) }}'
SNAPSHOTS_UPDATING_ENABLED: '{{ not (default false .Values.demoModeEnabled) }}'
DEMO_MODE_ENABLED: '{{ default false .Values.demoModeEnabled }}'
DETECT_DUPLICATES: '{{ .Values.tap.misc.detectDuplicates | ternary "true" "false" }}'
PCAP_DUMP_ENABLE: '{{ .Values.pcapdump.enabled }}'
PCAP_TIME_INTERVAL: '{{ .Values.pcapdump.timeInterval }}'
PCAP_MAX_TIME: '{{ .Values.pcapdump.maxTime }}'
PCAP_MAX_SIZE: '{{ .Values.pcapdump.maxSize }}'
PCAP_SRC_DIR: '{{ .Values.pcapdump.pcapSrcDir }}'
PORT_MAPPING: '{{ toJson .Values.tap.portMapping }}'
RAW_CAPTURE_ENABLED: '{{ .Values.tap.capture.raw.enabled | ternary "true" "false" }}'
RAW_CAPTURE_STORAGE_SIZE: '{{ .Values.tap.capture.raw.storageSize }}'

View File

@@ -1,14 +1,16 @@
kind: Secret
apiVersion: v1
metadata:
name: kubeshark-secret
name: {{ include "kubeshark.secretName" . }}
namespace: {{ .Release.Namespace }}
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
stringData:
LICENSE: '{{ .Values.license }}'
SCRIPTING_ENV: '{{ .Values.scripting.env | toJson }}'
OIDC_CLIENT_ID: '{{ default "not set" (((.Values.tap).auth).oidc).clientId }}'
OIDC_CLIENT_SECRET: '{{ default "not set" (((.Values.tap).auth).oidc).clientSecret }}'
---
@@ -18,7 +20,7 @@ metadata:
name: kubeshark-saml-x509-crt-secret
namespace: {{ .Release.Namespace }}
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
stringData:
AUTH_SAML_X509_CRT: |
@@ -32,7 +34,7 @@ metadata:
name: kubeshark-saml-x509-key-secret
namespace: {{ .Release.Namespace }}
labels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
stringData:
AUTH_SAML_X509_KEY: |

View File

@@ -14,7 +14,7 @@ metadata:
namespace: {{ .Release.Namespace }}
spec:
selector:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
{{- include "kubeshark.labels" . | nindent 4 }}
ports:
- name: metrics

View File

@@ -14,7 +14,7 @@ metadata:
namespace: {{ .Release.Namespace }}
spec:
selector:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
ports:
- name: metrics

View File

@@ -3,8 +3,8 @@ kind: NetworkPolicy
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-hub-network-policy
@@ -12,7 +12,7 @@ metadata:
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
policyTypes:
- Ingress
- Egress
@@ -40,7 +40,7 @@ metadata:
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
policyTypes:
- Ingress
- Egress
@@ -53,6 +53,31 @@ spec:
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
annotations:
{{- if .Values.tap.annotations }}
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-dex-network-policy
namespace: {{ .Release.Namespace }}
spec:
podSelector:
matchLabels:
app.kubeshark.com/app: dex
policyTypes:
- Ingress
- Egress
ingress:
- ports:
- protocol: TCP
port: 5556
egress:
- {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
@@ -65,7 +90,7 @@ metadata:
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
policyTypes:
- Ingress
- Egress

View File

@@ -0,0 +1,27 @@
{{ if .Values.tap.gitops.enabled -}}
apiVersion: batch/v1
kind: Job
metadata:
name: kubeshark-cleanup-job
annotations:
"helm.sh/hook": pre-delete
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
spec:
serviceAccountName: {{ include "kubeshark.serviceAccountName" . }}
{{- if .Values.tap.priorityClass }}
priorityClassName: {{ .Values.tap.priorityClass | quote }}
{{- end }}
restartPolicy: Never
containers:
- name: cleanup
{{- if .Values.tap.docker.overrideImage.hub }}
image: '{{ .Values.tap.docker.overrideImage.hub }}'
{{- else if .Values.tap.docker.overrideTag.hub }}
image: '{{ .Values.tap.docker.registry }}/hub:{{ .Values.tap.docker.overrideTag.hub }}'
{{ else }}
image: '{{ .Values.tap.docker.registry }}/hub:{{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (include "kubeshark.defaultVersion" .) }}'
{{- end }}
command: ["/app/cleanup"]
{{ end -}}

View File

@@ -0,0 +1,112 @@
{{- if .Values.tap.auth.dexConfig }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubeshark.com/app: dex
{{- include "kubeshark.labels" . | nindent 4 }}
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: {{ include "kubeshark.name" . }}-dex
namespace: {{ .Release.Namespace }}
spec:
replicas: 1 # Set the desired number of replicas
selector:
matchLabels:
app.kubeshark.com/app: dex
{{- include "kubeshark.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app.kubeshark.com/app: dex
{{- include "kubeshark.labels" . | nindent 8 }}
spec:
containers:
- name: kubeshark-dex
image: 'dexidp/dex:v2.42.0-alpine'
ports:
- name: http
containerPort: 5556
protocol: TCP
- name: telemetry
containerPort: 5558
protocol: TCP
args:
- dex
- serve
- /etc/dex/dex-config.yaml
imagePullPolicy: {{ .Values.tap.docker.imagePullPolicy }}
volumeMounts:
- name: dex-secret-conf-volume
mountPath: /etc/dex/dex-config.yaml
subPath: dex-config.yaml
readOnly: true
livenessProbe:
httpGet:
path: /healthz/live
port: 5558
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
readinessProbe:
httpGet:
path: /healthz/ready
port: 5558
periodSeconds: 1
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
timeoutSeconds: 1
resources:
limits:
cpu: 750m
memory: 1Gi
requests:
cpu: 50m
memory: 50Mi
{{- if gt (len .Values.tap.nodeSelectorTerms.dex) 0}}
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
{{- toYaml .Values.tap.nodeSelectorTerms.dex | nindent 12 }}
{{- end }}
{{- if or .Values.tap.dns.nameservers .Values.tap.dns.searches .Values.tap.dns.options }}
dnsConfig:
{{- if .Values.tap.dns.nameservers }}
nameservers:
{{- range .Values.tap.dns.nameservers }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.searches }}
searches:
{{- range .Values.tap.dns.searches }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- if .Values.tap.dns.options }}
options:
{{- range .Values.tap.dns.options }}
- name: {{ .name | quote }}
{{- if .value }}
value: {{ .value | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
volumes:
- name: dex-secret-conf-volume
secret:
secretName: kubeshark-dex-conf-secret
dnsPolicy: ClusterFirstWithHostNet
serviceAccountName: {{ include "kubeshark.serviceAccountName" . }}
{{- if .Values.tap.priorityClass }}
priorityClassName: {{ .Values.tap.priorityClass | quote }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,25 @@
{{- if .Values.tap.auth.dexConfig }}
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubeshark.com/app: dex
{{- include "kubeshark.labels" . | nindent 4 }}
{{- if .Values.tap.annotations }}
annotations:
{{- toYaml .Values.tap.annotations | nindent 4 }}
{{- end }}
name: kubeshark-dex
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: kubeshark-dex
port: 80
targetPort: 5556
selector:
app.kubeshark.com/app: dex
type: ClusterIP
{{- end }}

View File

@@ -0,0 +1,14 @@
{{- if .Values.tap.auth.dexConfig }}
kind: Secret
apiVersion: v1
metadata:
name: kubeshark-dex-conf-secret
namespace: {{ .Release.Namespace }}
labels:
app.kubeshark.com/app: hub
{{- include "kubeshark.labels" . | nindent 4 }}
data:
dex-config.yaml: {{ .Values.tap.auth.dexConfig | toYaml | b64enc | quote }}
{{- end }}

View File

@@ -0,0 +1,64 @@
{{- $hasConfigValues := or .Values.tap.snapshots.cloud.prefix .Values.tap.snapshots.cloud.s3.bucket .Values.tap.snapshots.cloud.s3.region .Values.tap.snapshots.cloud.s3.roleArn .Values.tap.snapshots.cloud.s3.externalId .Values.tap.snapshots.cloud.azblob.storageAccount .Values.tap.snapshots.cloud.azblob.container .Values.tap.snapshots.cloud.gcs.bucket .Values.tap.snapshots.cloud.gcs.project -}}
{{- $hasSecretValues := or .Values.tap.snapshots.cloud.s3.accessKey .Values.tap.snapshots.cloud.s3.secretKey .Values.tap.snapshots.cloud.azblob.storageKey .Values.tap.snapshots.cloud.gcs.credentialsJson -}}
{{- if $hasConfigValues }}
---
apiVersion: v1
kind: ConfigMap
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
name: {{ include "kubeshark.name" . }}-cloud-config
namespace: {{ .Release.Namespace }}
data:
{{- if .Values.tap.snapshots.cloud.prefix }}
SNAPSHOT_CLOUD_PREFIX: {{ .Values.tap.snapshots.cloud.prefix | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.s3.bucket }}
SNAPSHOT_AWS_BUCKET: {{ .Values.tap.snapshots.cloud.s3.bucket | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.s3.region }}
SNAPSHOT_AWS_REGION: {{ .Values.tap.snapshots.cloud.s3.region | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.s3.roleArn }}
SNAPSHOT_AWS_ROLE_ARN: {{ .Values.tap.snapshots.cloud.s3.roleArn | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.s3.externalId }}
SNAPSHOT_AWS_EXTERNAL_ID: {{ .Values.tap.snapshots.cloud.s3.externalId | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.azblob.storageAccount }}
SNAPSHOT_AZBLOB_STORAGE_ACCOUNT: {{ .Values.tap.snapshots.cloud.azblob.storageAccount | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.azblob.container }}
SNAPSHOT_AZBLOB_CONTAINER: {{ .Values.tap.snapshots.cloud.azblob.container | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.gcs.bucket }}
SNAPSHOT_GCS_BUCKET: {{ .Values.tap.snapshots.cloud.gcs.bucket | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.gcs.project }}
SNAPSHOT_GCS_PROJECT: {{ .Values.tap.snapshots.cloud.gcs.project | quote }}
{{- end }}
{{- end }}
{{- if $hasSecretValues }}
---
apiVersion: v1
kind: Secret
metadata:
labels:
{{- include "kubeshark.labels" . | nindent 4 }}
name: {{ include "kubeshark.name" . }}-cloud-secret
namespace: {{ .Release.Namespace }}
type: Opaque
stringData:
{{- if .Values.tap.snapshots.cloud.s3.accessKey }}
SNAPSHOT_AWS_ACCESS_KEY: {{ .Values.tap.snapshots.cloud.s3.accessKey | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.s3.secretKey }}
SNAPSHOT_AWS_SECRET_KEY: {{ .Values.tap.snapshots.cloud.s3.secretKey | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.azblob.storageKey }}
SNAPSHOT_AZBLOB_STORAGE_KEY: {{ .Values.tap.snapshots.cloud.azblob.storageKey | quote }}
{{- end }}
{{- if .Values.tap.snapshots.cloud.gcs.credentialsJson }}
SNAPSHOT_GCS_CREDENTIALS_JSON: {{ .Values.tap.snapshots.cloud.gcs.credentialsJson | quote }}
{{- end }}
{{- end }}

View File

@@ -2,29 +2,42 @@ Thank you for installing {{ title .Chart.Name }}.
Registry: {{ .Values.tap.docker.registry }}
Tag: {{ not (eq .Values.tap.docker.tag "") | ternary .Values.tap.docker.tag (printf "v%s" .Chart.Version) }}
{{- if .Values.tap.docker.overrideTag.worker }}
Overridden worker tag: {{ .Values.tap.docker.overrideTag.worker }}
{{ end }}
{{- end }}
{{- if .Values.tap.docker.overrideTag.hub }}
Overridden hub tag: {{ .Values.tap.docker.overrideTag.hub }}
{{ end }}
{{- end }}
{{- if .Values.tap.docker.overrideTag.front }}
Overridden front tag: {{ .Values.tap.docker.overrideTag.front }}
{{ end }}
{{- end }}
{{- if .Values.tap.docker.overrideImage.worker }}
Overridden worker image: {{ .Values.tap.docker.overrideImage.worker }}
{{- end }}
{{- if .Values.tap.docker.overrideImage.hub }}
Overridden hub image: {{ .Values.tap.docker.overrideImage.hub }}
{{- end }}
{{- if .Values.tap.docker.overrideImage.front }}
Overridden front image: {{ .Values.tap.docker.overrideImage.front }}
{{- end }}
Your deployment has been successful. The release is named `{{ .Release.Name }}` and it has been deployed in the `{{ .Release.Namespace }}` namespace.
{{- if .Values.tap.telemetry.enabled }}
Notice: Telemetry is enabled. Kubeshark will collect anonymous usage statistics.
{{ end }}
{{- if .Values.tap.ingress.enabled }}
Notices:
{{- if .Values.supportChatEnabled}}
- Support chat using Intercom is enabled. It can be disabled using `--set supportChatEnabled=false`
{{- end }}
{{- if eq .Values.license ""}}
- No license key was detected.
- Authenticate through the dashboard to activate a complementary COMMUNITY license.
- If you have an Enterprise license, download the license key from https://console.kubeshark.com/
- An Enterprise license-key can be added as 'license: <license>' in helm values or as `--set license=<license>` or as `LICENSE` via mounted secret (`tap.secrets`).
- Contact us to get an Enterprise license: https://kubeshark.com/contact-us.
{{- end }}
{{ if .Values.tap.ingress.enabled }}
You can now access the application through the following URL:
http{{ if .Values.tap.ingress.tls }}s{{ end }}://{{ .Values.tap.ingress.host }}
http{{ if .Values.tap.ingress.tls }}s{{ end }}://{{ .Values.tap.ingress.host }}{{ default "" (((.Values.tap).routing).front).basePath }}/
{{- else }}
To access the application, follow these steps:
@@ -32,8 +45,9 @@ To access the application, follow these steps:
1. Perform port forwarding with the following commands:
kubectl port-forward -n {{ .Release.Namespace }} service/kubeshark-front 8899:80
you could also run: `kubeshark proxy` (which simply manages the port-forward connection)
2. Once port forwarding is done, you can access the application by visiting the following URL in your web browser:
http://0.0.0.0:8899
http://127.0.0.1:8899{{ default "" (((.Values.tap).routing).front).basePath }}/
{{ end }}
{{- end }}

View File

@@ -49,6 +49,18 @@ Create the name of the service account to use
{{- printf "%s-service-account" .Release.Name }}
{{- end }}
{{/*
Set configmap and secret names based on gitops.enabled
*/}}
{{- define "kubeshark.configmapName" -}}
kubeshark-config-map{{ if .Values.tap.gitops.enabled }}-default{{ end }}
{{- end -}}
{{- define "kubeshark.secretName" -}}
kubeshark-secret{{ if .Values.tap.gitops.enabled }}-default{{ end }}
{{- end -}}
{{/*
Escape double quotes in a string
*/}}
@@ -68,7 +80,7 @@ Create docker tag default version
*/}}
{{- define "kubeshark.defaultVersion" -}}
{{- $defaultVersion := (printf "v%s" .Chart.Version) -}}
{{- if not .Values.tap.docker.tagLocked }}
{{- if .Values.tap.docker.tagLocked }}
{{- $defaultVersion = regexReplaceAll "^([^.]+\\.[^.]+).*" $defaultVersion "$1" -}}
{{- end }}
{{- $defaultVersion }}
@@ -86,3 +98,15 @@ Set sentry based on internet connectivity and telemetry
{{- end -}}
{{- $sentryEnabledVal -}}
{{- end -}}
{{/*
Dex IdP: retrieve a secret for static client with a specific ID
*/}}
{{- define "getDexKubesharkStaticClientSecret" -}}
{{- $clientId := .clientId -}}
{{- range .clients }}
{{- if eq .id $clientId }}
{{- .secret }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,248 @@
suite: cloud storage template
templates:
- templates/21-cloud-storage.yaml
tests:
- it: should render nothing with default values
asserts:
- hasDocuments:
count: 0
- it: should render ConfigMap with S3 config only
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.region: us-east-1
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: metadata.name
value: RELEASE-NAME-cloud-config
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_BUCKET
value: "my-bucket"
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_REGION
value: "us-east-1"
documentIndex: 0
- notExists:
path: data.SNAPSHOT_AWS_ACCESS_KEY
documentIndex: 0
- it: should render ConfigMap and Secret with S3 config and credentials
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.region: us-east-1
tap.snapshots.cloud.s3.accessKey: AKIAIOSFODNN7EXAMPLE
tap.snapshots.cloud.s3.secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
asserts:
- hasDocuments:
count: 2
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_BUCKET
value: "my-bucket"
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_REGION
value: "us-east-1"
documentIndex: 0
- isKind:
of: Secret
documentIndex: 1
- equal:
path: metadata.name
value: RELEASE-NAME-cloud-secret
documentIndex: 1
- equal:
path: stringData.SNAPSHOT_AWS_ACCESS_KEY
value: "AKIAIOSFODNN7EXAMPLE"
documentIndex: 1
- equal:
path: stringData.SNAPSHOT_AWS_SECRET_KEY
value: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
documentIndex: 1
- it: should render ConfigMap with Azure Blob config only
set:
tap.snapshots.cloud.azblob.storageAccount: myaccount
tap.snapshots.cloud.azblob.container: mycontainer
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_AZBLOB_STORAGE_ACCOUNT
value: "myaccount"
documentIndex: 0
- equal:
path: data.SNAPSHOT_AZBLOB_CONTAINER
value: "mycontainer"
documentIndex: 0
- it: should render ConfigMap and Secret with Azure Blob config and storage key
set:
tap.snapshots.cloud.azblob.storageAccount: myaccount
tap.snapshots.cloud.azblob.container: mycontainer
tap.snapshots.cloud.azblob.storageKey: c29tZWtleQ==
asserts:
- hasDocuments:
count: 2
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_AZBLOB_STORAGE_ACCOUNT
value: "myaccount"
documentIndex: 0
- isKind:
of: Secret
documentIndex: 1
- equal:
path: stringData.SNAPSHOT_AZBLOB_STORAGE_KEY
value: "c29tZWtleQ=="
documentIndex: 1
- it: should render ConfigMap with GCS config only
set:
tap.snapshots.cloud.gcs.bucket: my-gcs-bucket
tap.snapshots.cloud.gcs.project: my-gcp-project
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_GCS_BUCKET
value: "my-gcs-bucket"
documentIndex: 0
- equal:
path: data.SNAPSHOT_GCS_PROJECT
value: "my-gcp-project"
documentIndex: 0
- notExists:
path: data.SNAPSHOT_GCS_CREDENTIALS_JSON
documentIndex: 0
- it: should render ConfigMap and Secret with GCS config and credentials
set:
tap.snapshots.cloud.gcs.bucket: my-gcs-bucket
tap.snapshots.cloud.gcs.project: my-gcp-project
tap.snapshots.cloud.gcs.credentialsJson: '{"type":"service_account"}'
asserts:
- hasDocuments:
count: 2
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_GCS_BUCKET
value: "my-gcs-bucket"
documentIndex: 0
- equal:
path: data.SNAPSHOT_GCS_PROJECT
value: "my-gcp-project"
documentIndex: 0
- isKind:
of: Secret
documentIndex: 1
- equal:
path: metadata.name
value: RELEASE-NAME-cloud-secret
documentIndex: 1
- equal:
path: stringData.SNAPSHOT_GCS_CREDENTIALS_JSON
value: '{"type":"service_account"}'
documentIndex: 1
- it: should render ConfigMap with GCS bucket only (no project)
set:
tap.snapshots.cloud.gcs.bucket: my-gcs-bucket
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_GCS_BUCKET
value: "my-gcs-bucket"
documentIndex: 0
- notExists:
path: data.SNAPSHOT_GCS_PROJECT
documentIndex: 0
- it: should render ConfigMap with only prefix
set:
tap.snapshots.cloud.prefix: snapshots/prod
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_CLOUD_PREFIX
value: "snapshots/prod"
documentIndex: 0
- notExists:
path: data.SNAPSHOT_AWS_BUCKET
documentIndex: 0
- notExists:
path: data.SNAPSHOT_AZBLOB_STORAGE_ACCOUNT
documentIndex: 0
- notExists:
path: data.SNAPSHOT_GCS_BUCKET
documentIndex: 0
- it: should render ConfigMap with role ARN without credentials (IAM auth)
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.region: us-east-1
tap.snapshots.cloud.s3.roleArn: arn:aws:iam::123456789012:role/my-role
asserts:
- hasDocuments:
count: 1
- isKind:
of: ConfigMap
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_ROLE_ARN
value: "arn:aws:iam::123456789012:role/my-role"
documentIndex: 0
- equal:
path: data.SNAPSHOT_AWS_BUCKET
value: "my-bucket"
documentIndex: 0
- it: should render ConfigMap with externalId
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.externalId: ext-12345
asserts:
- hasDocuments:
count: 1
- equal:
path: data.SNAPSHOT_AWS_EXTERNAL_ID
value: "ext-12345"
documentIndex: 0
- it: should set correct namespace
release:
namespace: kubeshark-ns
set:
tap.snapshots.cloud.s3.bucket: my-bucket
asserts:
- equal:
path: metadata.namespace
value: kubeshark-ns
documentIndex: 0

View File

@@ -0,0 +1,127 @@
suite: dissection storage configuration
templates:
- templates/04-hub-deployment.yaml
tests:
- it: should fallback to snapshot storageSize when dissection storageSize is empty
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-size
- contains:
path: spec.template.spec.containers[0].command
content: "20Gi"
- it: should fallback to snapshot storageClass when dissection storageClass is empty
set:
tap.snapshots.local.storageClass: gp2
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-class
- contains:
path: spec.template.spec.containers[0].command
content: gp2
- it: should not render dissector-storage-class when both dissection and snapshot storageClass are empty
asserts:
- notContains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-class
- it: should prefer dissection storageSize over snapshot storageSize
set:
tap.delayedDissection.storageSize: 100Gi
tap.snapshots.local.storageSize: 50Gi
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-size
- contains:
path: spec.template.spec.containers[0].command
content: "100Gi"
- it: should prefer dissection storageClass over snapshot storageClass
set:
tap.delayedDissection.storageClass: io2
tap.snapshots.local.storageClass: gp2
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-class
- contains:
path: spec.template.spec.containers[0].command
content: io2
- it: should fallback to snapshot config for both storageSize and storageClass
set:
tap.snapshots.local.storageSize: 30Gi
tap.snapshots.local.storageClass: gp3
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-size
- contains:
path: spec.template.spec.containers[0].command
content: "30Gi"
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-class
- contains:
path: spec.template.spec.containers[0].command
content: gp3
- it: should not render dissector-storage-size when both dissection and snapshot storageSize are empty
set:
tap.delayedDissection.storageSize: ""
tap.snapshots.local.storageSize: ""
asserts:
- notContains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-size
- it: should render all dissector args together with custom values
set:
tap.delayedDissection.cpu: "4"
tap.delayedDissection.memory: 8Gi
tap.delayedDissection.storageSize: 200Gi
tap.delayedDissection.storageClass: local-path
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-cpu
- contains:
path: spec.template.spec.containers[0].command
content: "4"
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-memory
- contains:
path: spec.template.spec.containers[0].command
content: 8Gi
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-size
- contains:
path: spec.template.spec.containers[0].command
content: "200Gi"
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-storage-class
- contains:
path: spec.template.spec.containers[0].command
content: local-path
- it: should still render existing dissector-cpu and dissector-memory args
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-cpu
- contains:
path: spec.template.spec.containers[0].command
content: "1"
- contains:
path: spec.template.spec.containers[0].command
content: -dissector-memory
- contains:
path: spec.template.spec.containers[0].command
content: 4Gi

View File

@@ -0,0 +1,9 @@
tap:
snapshots:
cloud:
provider: azblob
prefix: snapshots/
azblob:
storageAccount: kubesharkstore
container: snapshots
storageKey: c29tZWtleWhlcmU=

View File

@@ -0,0 +1,8 @@
tap:
snapshots:
cloud:
provider: s3
configMaps:
- my-cloud-config
secrets:
- my-cloud-secret

View File

@@ -0,0 +1,9 @@
tap:
snapshots:
cloud:
provider: gcs
prefix: snapshots/
gcs:
bucket: kubeshark-snapshots
project: my-gcp-project
credentialsJson: '{"type":"service_account","project_id":"my-gcp-project"}'

View File

@@ -0,0 +1,10 @@
tap:
snapshots:
cloud:
provider: s3
prefix: snapshots/
s3:
bucket: kubeshark-snapshots
region: us-east-1
accessKey: AKIAIOSFODNN7EXAMPLE
secretKey: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

View File

@@ -0,0 +1,167 @@
suite: hub deployment cloud integration
templates:
- templates/04-hub-deployment.yaml
tests:
- it: should not render envFrom with default values
asserts:
- isKind:
of: Deployment
- notContains:
path: spec.template.spec.containers[0].envFrom
any: true
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- it: should render envFrom with inline S3 config
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.region: us-east-1
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- it: should render envFrom secret ref with inline credentials
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.accessKey: AKIAIOSFODNN7EXAMPLE
tap.snapshots.cloud.s3.secretKey: secret
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: RELEASE-NAME-cloud-secret
- it: should render envFrom with inline GCS config
set:
tap.snapshots.cloud.gcs.bucket: my-gcs-bucket
tap.snapshots.cloud.gcs.project: my-gcp-project
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- it: should render envFrom secret ref with inline GCS credentials
set:
tap.snapshots.cloud.gcs.bucket: my-gcs-bucket
tap.snapshots.cloud.gcs.credentialsJson: '{"type":"service_account"}'
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: RELEASE-NAME-cloud-secret
- it: should render cloud-storage-provider arg when provider is gcs
set:
tap.snapshots.cloud.provider: gcs
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -cloud-storage-provider
- contains:
path: spec.template.spec.containers[0].command
content: gcs
- it: should render envFrom with external configMaps
set:
tap.snapshots.cloud.configMaps:
- my-cloud-config
- my-other-config
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: my-cloud-config
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: my-other-config
- it: should render envFrom with external secrets
set:
tap.snapshots.cloud.secrets:
- my-cloud-secret
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: my-cloud-secret
- it: should render cloud-storage-provider arg when provider is set
set:
tap.snapshots.cloud.provider: s3
asserts:
- contains:
path: spec.template.spec.containers[0].command
content: -cloud-storage-provider
- contains:
path: spec.template.spec.containers[0].command
content: s3
- it: should not render cloud-storage-provider arg with default values
asserts:
- notContains:
path: spec.template.spec.containers[0].command
content: -cloud-storage-provider
- it: should render envFrom with tap.secrets
set:
tap.secrets:
- my-existing-secret
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: my-existing-secret
- it: should render both inline and external refs together
set:
tap.snapshots.cloud.s3.bucket: my-bucket
tap.snapshots.cloud.s3.accessKey: key
tap.snapshots.cloud.s3.secretKey: secret
tap.snapshots.cloud.configMaps:
- ext-config
tap.snapshots.cloud.secrets:
- ext-secret
asserts:
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: ext-config
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: ext-secret
- contains:
path: spec.template.spec.containers[0].envFrom
content:
configMapRef:
name: RELEASE-NAME-cloud-config
- contains:
path: spec.template.spec.containers[0].envFrom
content:
secretRef:
name: RELEASE-NAME-cloud-secret

View File

@@ -1,4 +1,3 @@
# find a detailed description here: https://github.com/kubeshark/kubeshark/blob/master/helm-chart/README.md
tap:
docker:
registry: docker.io/kubeshark
@@ -26,17 +25,61 @@ tap:
namespaces: []
excludedNamespaces: []
bpfOverride: ""
stopped: false
capture:
dissection:
enabled: true
stopAfter: 5m
captureSelf: false
raw:
enabled: true
storageSize: 1Gi
dbMaxSize: 500Mi
delayedDissection:
cpu: "1"
memory: 4Gi
storageSize: ""
storageClass: ""
snapshots:
local:
storageClass: ""
storageSize: 20Gi
cloud:
provider: ""
prefix: ""
configMaps: []
secrets: []
s3:
bucket: ""
region: ""
accessKey: ""
secretKey: ""
roleArn: ""
externalId: ""
azblob:
storageAccount: ""
container: ""
storageKey: ""
gcs:
bucket: ""
project: ""
credentialsJson: ""
release:
repo: https://helm.kubeshark.co
repo: https://helm.kubeshark.com
name: kubeshark
namespace: default
helmChartPath: ""
persistentStorage: false
persistentStorageStatic: false
persistentStoragePvcVolumeMode: FileSystem
efsFileSytemIdAndPath: ""
storageLimit: 5000Mi
secrets: []
storageLimit: 10Gi
storageClass: standard
dryRun: false
dns:
nameservers: []
searches: []
options: []
resources:
hub:
limits:
@@ -59,73 +102,107 @@ tap:
requests:
cpu: 50m
memory: 50Mi
probes:
hub:
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
sniffer:
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
serviceMesh: true
tls: true
disableTlsLog: true
packetCapture: best
ignoreTainted: false
labels: {}
annotations: {}
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
hub:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
workers:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
front:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
dex:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
tolerations:
hub: []
workers:
- operator: Exists
effect: NoExecute
front: []
auth:
enabled: false
type: saml
roles:
admin:
filter: ""
canDownloadPCAP: true
canUseScripting: true
scriptingPermissions:
canSave: true
canActivate: true
canDelete: true
canUpdateTargetedPods: true
canStopTrafficCapturing: true
canControlDissection: true
showAdminConsoleLink: true
rolesClaim: role
defaultRole: ""
defaultFilter: ""
saml:
idpMetadataUrl: ""
x509crt: ""
x509key: ""
roleAttribute: role
roles:
admin:
filter: ""
canDownloadPCAP: true
canUseScripting: true
scriptingPermissions:
canSave: true
canActivate: true
canDelete: true
canUpdateTargetedPods: true
canStopTrafficCapturing: true
showAdminConsoleLink: true
ingress:
enabled: false
className: ""
host: ks.svc.cluster.local
path: /
tls: []
annotations: {}
priorityClass: ""
routing:
front:
basePath: ""
ipv6: true
debug: false
dashboard:
streamingType: connect-rpc
completeStreamingEnabled: true
clusterWideMapEnabled: false
entriesLimit: "300000"
telemetry:
enabled: true
resourceGuard:
enabled: false
watchdog:
enabled: false
gitops:
enabled: false
sentry:
enabled: false
environment: production
defaultFilter: "!dns and !error"
scriptingDisabled: false
targetedPodsUpdateDisabled: false
presetFiltersChangingEnabled: true
recordingDisabled: false
stopTrafficCapturingDisabled: false
capabilities:
networkCapture:
- NET_RAW
- NET_ADMIN
serviceMeshCapture:
- SYS_ADMIN
- SYS_PTRACE
- DAC_OVERRIDE
ebpfCapture:
- SYS_ADMIN
- SYS_PTRACE
- SYS_RESOURCE
- IPC_LOCK
defaultFilter: ""
globalFilter: ""
enabledDissectors:
- amqp
@@ -133,11 +210,41 @@ tap:
- http
- icmp
- kafka
- mongodb
- mysql
- postgresql
- redis
- sctp
- syscall
- ws
- tlsx
- ldap
- radius
- diameter
- udp-flow
- tcp-flow
- udp-conn
- tcp-conn
portMapping:
http:
- 80
- 443
- 8080
amqp:
- 5671
- 5672
kafka:
- 9092
mongodb:
- 27017
mysql:
- 3306
postgresql:
- 5432
redis:
- 6379
ldap:
- 389
diameter:
- 3868
customMacros:
https: tls and (http or http2)
metrics:
@@ -148,8 +255,8 @@ tap:
view: flamegraph
misc:
jsonTTL: 5m
pcapTTL: 10s
pcapErrorTTL: 60s
pcapTTL: "0"
pcapErrorTTL: "0"
trafficSampleRate: 100
tcpStreamChannelTimeoutMs: 10000
tcpStreamChannelTimeoutShow: false
@@ -157,27 +264,58 @@ tap:
duplicateTimeframe: 200ms
detectDuplicates: false
staleTimeoutSeconds: 30
tcpFlowTimeout: 1200
udpFlowTimeout: 1200
securityContext:
privileged: true
appArmorProfile:
type: ""
localhostProfile: ""
seLinuxOptions:
level: ""
role: ""
type: ""
user: ""
capabilities:
networkCapture:
- NET_RAW
- NET_ADMIN
serviceMeshCapture:
- SYS_ADMIN
- SYS_PTRACE
- DAC_OVERRIDE
ebpfCapture:
- SYS_ADMIN
- SYS_PTRACE
- SYS_RESOURCE
- IPC_LOCK
mountBpf: true
hostNetwork: true
logs:
file: ""
grep: ""
pcapdump:
enabled: true
enabled: false
timeInterval: 1m
maxTime: 1h
maxSize: 500MB
pcapSrcDir: pcapdump
time: time
debug: false
dest: ""
kube:
configPath: ""
context: ""
dumpLogs: false
headless: false
license: ""
cloudApiUrl: https://api.kubeshark.com
cloudLicenseEnabled: true
supportChatEnabled: true
demoModeEnabled: false
supportChatEnabled: false
betaEnabled: false
internetConnectivity: true
dissectorsUpdatingEnabled: true
scripting:
enabled: false
env: {}
source: ""
sources: []

57
integration/README.md Normal file
View File

@@ -0,0 +1,57 @@
# Integration Tests
This directory contains integration tests that run against a real Kubernetes cluster.
## Prerequisites
1. **Kubernetes cluster** - A running cluster accessible via `kubectl`
2. **kubectl** - Configured with appropriate context
3. **Go 1.21+** - For running tests
## Running Tests
```bash
# Run all integration tests
make test-integration
# Run specific command tests
make test-integration-mcp
# Run with verbose output
make test-integration-verbose
# Run with custom timeout (default: 5m)
INTEGRATION_TIMEOUT=10m make test-integration
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `KUBESHARK_BINARY` | Auto-built | Path to pre-built kubeshark binary |
| `INTEGRATION_TIMEOUT` | `5m` | Test timeout duration |
| `KUBECONFIG` | `~/.kube/config` | Kubernetes config file |
| `INTEGRATION_SKIP_CLEANUP` | `false` | Skip cleanup after tests (for debugging) |
## Test Structure
```
integration/
├── README.md # This file
├── common_test.go # Shared test helpers
├── mcp_test.go # MCP command integration tests
├── tap_test.go # Tap command tests (future)
└── ... # Additional command tests
```
## Writing New Tests
1. Create `<command>_test.go` with build tag `//go:build integration`
2. Use helpers from `common_test.go`: `requireKubernetesCluster(t)`, `getKubesharkBinary(t)`, `cleanupKubeshark(t, binary)`
## CI/CD Integration
```bash
# JSON output for CI parsing
go test -tags=integration -json ./integration/...
```

217
integration/common_test.go Normal file
View File

@@ -0,0 +1,217 @@
//go:build integration
// Package integration contains integration tests that run against a real Kubernetes cluster.
// Run with: go test -tags=integration ./integration/...
package integration
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
const (
binaryName = "kubeshark"
defaultTimeout = 2 * time.Minute
startupTimeout = 3 * time.Minute
)
var (
// binaryPath caches the built binary path
binaryPath string
buildOnce sync.Once
buildErr error
)
// requireKubernetesCluster skips the test if no Kubernetes cluster is available.
func requireKubernetesCluster(t *testing.T) {
t.Helper()
if !hasKubernetesCluster() {
t.Skip("Skipping: no Kubernetes cluster available")
}
}
// hasKubernetesCluster returns true if a Kubernetes cluster is accessible.
func hasKubernetesCluster() bool {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
return exec.CommandContext(ctx, "kubectl", "cluster-info").Run() == nil
}
// getKubesharkBinary returns the path to the kubeshark binary, building it if necessary.
func getKubesharkBinary(t *testing.T) string {
t.Helper()
// Check if binary path is provided via environment
if envBinary := os.Getenv("KUBESHARK_BINARY"); envBinary != "" {
if _, err := os.Stat(envBinary); err == nil {
return envBinary
}
t.Fatalf("KUBESHARK_BINARY set but file not found: %s", envBinary)
}
// Build once per test run
buildOnce.Do(func() {
binaryPath, buildErr = buildBinary(t)
})
if buildErr != nil {
t.Fatalf("Failed to build binary: %v", buildErr)
}
return binaryPath
}
// buildBinary compiles the binary and returns its path.
func buildBinary(t *testing.T) (string, error) {
t.Helper()
// Find project root (directory containing go.mod)
projectRoot, err := findProjectRoot()
if err != nil {
return "", fmt.Errorf("finding project root: %w", err)
}
outputPath := filepath.Join(projectRoot, "bin", binaryName+"_integration_test")
t.Logf("Building %s binary at %s", binaryName, outputPath)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, "go", "build",
"-o", outputPath,
filepath.Join(projectRoot, binaryName+".go"),
)
cmd.Dir = projectRoot
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("build failed: %w\nOutput: %s", err, output)
}
return outputPath, nil
}
// findProjectRoot locates the project root by finding go.mod
func findProjectRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("could not find go.mod in any parent directory")
}
dir = parent
}
}
// runKubeshark executes the kubeshark binary with the given arguments.
// Returns combined stdout/stderr and any error.
func runKubeshark(t *testing.T, binary string, args ...string) (string, error) {
t.Helper()
return runKubesharkWithTimeout(t, binary, defaultTimeout, args...)
}
// runKubesharkWithTimeout executes the kubeshark binary with a custom timeout.
func runKubesharkWithTimeout(t *testing.T, binary string, timeout time.Duration, args ...string) (string, error) {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
t.Logf("Running: %s %s", binary, strings.Join(args, " "))
cmd := exec.CommandContext(ctx, binary, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
output := stdout.String()
if stderr.Len() > 0 {
output += "\n[stderr]\n" + stderr.String()
}
if ctx.Err() == context.DeadlineExceeded {
return output, fmt.Errorf("command timed out after %v", timeout)
}
return output, err
}
// cleanupKubeshark ensures Kubeshark is not running in the cluster.
func cleanupKubeshark(t *testing.T, binary string) {
t.Helper()
if os.Getenv("INTEGRATION_SKIP_CLEANUP") == "true" {
t.Log("Skipping cleanup (INTEGRATION_SKIP_CLEANUP=true)")
return
}
t.Log("Cleaning up any existing Kubeshark installation...")
// Run clean command, ignore errors (might not be installed)
_, _ = runKubeshark(t, binary, "clean")
// Wait a moment for resources to be deleted
time.Sleep(2 * time.Second)
}
// waitForKubesharkReady waits for Kubeshark to be ready after starting.
func waitForKubesharkReady(t *testing.T, binary string, timeout time.Duration) error {
t.Helper()
t.Log("Waiting for Kubeshark to be ready...")
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
// Check if pods are running
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
cmd := exec.CommandContext(ctx, "kubectl", "get", "pods", "-l", "app.kubernetes.io/name=kubeshark", "-o", "jsonpath={.items[*].status.phase}")
output, err := cmd.Output()
cancel()
if err == nil && strings.Contains(string(output), "Running") {
t.Log("Kubeshark is ready")
return nil
}
time.Sleep(5 * time.Second)
}
return fmt.Errorf("timeout waiting for Kubeshark to be ready")
}
// isKubesharkRunning checks if Kubeshark is currently running in the cluster.
func isKubesharkRunning(t *testing.T) bool {
t.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "kubectl", "get", "pods", "-l", "app.kubernetes.io/name=kubeshark", "-o", "name")
output, err := cmd.Output()
if err != nil {
return false
}
return strings.TrimSpace(string(output)) != ""
}

529
integration/mcp_test.go Normal file
View File

@@ -0,0 +1,529 @@
//go:build integration
package integration
import (
"bufio"
"bytes"
"context"
"encoding/json"
"io"
"os/exec"
"strings"
"testing"
"time"
)
// MCPRequest represents a JSON-RPC request
type MCPRequest struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params interface{} `json:"params,omitempty"`
}
// MCPResponse represents a JSON-RPC response
type MCPResponse struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Result json.RawMessage `json:"result,omitempty"`
Error *MCPError `json:"error,omitempty"`
}
// MCPError represents a JSON-RPC error
type MCPError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// mcpSession represents a running MCP server session
type mcpSession struct {
cmd *exec.Cmd
stdin io.WriteCloser
stdout *bufio.Reader
stderr *bytes.Buffer // Captured stderr for debugging
cancel context.CancelFunc
}
// startMCPSession starts an MCP server and returns a session for sending requests.
// By default, starts in read-only mode (no --allow-destructive).
func startMCPSession(t *testing.T, binary string, args ...string) *mcpSession {
t.Helper()
ctx, cancel := context.WithCancel(context.Background())
cmdArgs := append([]string{"mcp"}, args...)
cmd := exec.CommandContext(ctx, binary, cmdArgs...)
stdin, err := cmd.StdinPipe()
if err != nil {
cancel()
t.Fatalf("Failed to create stdin pipe: %v", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
cancel()
t.Fatalf("Failed to create stdout pipe: %v", err)
}
// Capture stderr for debugging
var stderrBuf bytes.Buffer
cmd.Stderr = &stderrBuf
if err := cmd.Start(); err != nil {
cancel()
t.Fatalf("Failed to start MCP server: %v", err)
}
return &mcpSession{
cmd: cmd,
stdin: stdin,
stdout: bufio.NewReader(stdout),
stderr: &stderrBuf,
cancel: cancel,
}
}
// startMCPSessionWithDestructive starts an MCP server with --allow-destructive flag.
func startMCPSessionWithDestructive(t *testing.T, binary string, args ...string) *mcpSession {
t.Helper()
allArgs := append([]string{"--allow-destructive"}, args...)
return startMCPSession(t, binary, allArgs...)
}
// sendRequest sends a JSON-RPC request and returns the response (30s timeout).
func (s *mcpSession) sendRequest(t *testing.T, req MCPRequest) MCPResponse {
t.Helper()
return s.sendRequestWithTimeout(t, req, 30*time.Second)
}
// sendRequestWithTimeout sends a JSON-RPC request with a custom timeout.
func (s *mcpSession) sendRequestWithTimeout(t *testing.T, req MCPRequest, timeout time.Duration) MCPResponse {
t.Helper()
reqBytes, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal request: %v", err)
}
t.Logf("Sending: %s", string(reqBytes))
if _, err := s.stdin.Write(append(reqBytes, '\n')); err != nil {
t.Fatalf("Failed to write request: %v", err)
}
// Read response with timeout
responseChan := make(chan string, 1)
errChan := make(chan error, 1)
go func() {
line, err := s.stdout.ReadString('\n')
if err != nil {
errChan <- err
return
}
responseChan <- line
}()
select {
case line := <-responseChan:
t.Logf("Received: %s", strings.TrimSpace(line))
var resp MCPResponse
if err := json.Unmarshal([]byte(line), &resp); err != nil {
t.Fatalf("Failed to unmarshal response: %v\nResponse: %s", err, line)
}
return resp
case err := <-errChan:
t.Fatalf("Failed to read response: %v", err)
return MCPResponse{}
case <-time.After(timeout):
t.Fatalf("Timeout waiting for MCP response after %v", timeout)
return MCPResponse{}
}
}
// callTool invokes an MCP tool and returns the response (30s timeout).
func (s *mcpSession) callTool(t *testing.T, id int, toolName string, args map[string]interface{}) MCPResponse {
t.Helper()
return s.callToolWithTimeout(t, id, toolName, args, 30*time.Second)
}
// callToolWithTimeout invokes an MCP tool with a custom timeout.
func (s *mcpSession) callToolWithTimeout(t *testing.T, id int, toolName string, args map[string]interface{}, timeout time.Duration) MCPResponse {
t.Helper()
return s.sendRequestWithTimeout(t, MCPRequest{
JSONRPC: "2.0",
ID: id,
Method: "tools/call",
Params: map[string]interface{}{
"name": toolName,
"arguments": args,
},
}, timeout)
}
// close terminates the MCP session.
func (s *mcpSession) close() {
s.cancel()
_ = s.cmd.Wait()
}
// getStderr returns any captured stderr output (useful for debugging failures).
func (s *mcpSession) getStderr() string {
if s.stderr == nil {
return ""
}
return s.stderr.String()
}
// initialize sends the MCP initialize request and returns the response.
func (s *mcpSession) initialize(t *testing.T, id int) MCPResponse {
t.Helper()
return s.sendRequest(t, MCPRequest{
JSONRPC: "2.0",
ID: id,
Method: "initialize",
Params: map[string]interface{}{
"protocolVersion": "2024-11-05",
"capabilities": map[string]interface{}{},
"clientInfo": map[string]interface{}{"name": "test", "version": "1.0"},
},
})
}
// TestMCP_Initialize tests the MCP initialization handshake.
func TestMCP_Initialize(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSession(t, getKubesharkBinary(t))
defer session.close()
resp := session.initialize(t, 1)
if resp.Error != nil {
t.Fatalf("Initialize failed: %s", resp.Error.Message)
}
var result map[string]interface{}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
if _, ok := result["capabilities"]; !ok {
t.Error("Response missing capabilities")
}
if _, ok := result["serverInfo"]; !ok {
t.Error("Response missing serverInfo")
}
}
// TestMCP_ToolsList_ReadOnly tests that tools/list returns only safe tools in read-only mode.
func TestMCP_ToolsList_ReadOnly(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSession(t, getKubesharkBinary(t))
defer session.close()
session.initialize(t, 1)
resp := session.sendRequest(t, MCPRequest{JSONRPC: "2.0", ID: 2, Method: "tools/list"})
if resp.Error != nil {
t.Fatalf("tools/list failed: %s", resp.Error.Message)
}
var result struct {
Tools []struct{ Name string `json:"name"` } `json:"tools"`
}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
toolNames := make(map[string]bool)
for _, tool := range result.Tools {
toolNames[tool.Name] = true
}
if !toolNames["check_kubeshark_status"] {
t.Error("Missing expected tool: check_kubeshark_status")
}
if toolNames["start_kubeshark"] || toolNames["stop_kubeshark"] {
t.Error("Destructive tools should not be available in read-only mode")
}
}
// TestMCP_ToolsList_WithDestructive tests that tools/list includes destructive tools when flag is set.
func TestMCP_ToolsList_WithDestructive(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSessionWithDestructive(t, getKubesharkBinary(t))
defer session.close()
session.initialize(t, 1)
resp := session.sendRequest(t, MCPRequest{JSONRPC: "2.0", ID: 2, Method: "tools/list"})
if resp.Error != nil {
t.Fatalf("tools/list failed: %s", resp.Error.Message)
}
var result struct {
Tools []struct{ Name string `json:"name"` } `json:"tools"`
}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
toolNames := make(map[string]bool)
for _, tool := range result.Tools {
toolNames[tool.Name] = true
}
for _, expected := range []string{"check_kubeshark_status", "start_kubeshark", "stop_kubeshark"} {
if !toolNames[expected] {
t.Errorf("Missing expected tool: %s", expected)
}
}
}
// TestMCP_CheckKubesharkStatus_NotRunning tests check_kubeshark_status when Kubeshark is not running.
func TestMCP_CheckKubesharkStatus_NotRunning(t *testing.T) {
requireKubernetesCluster(t)
binary := getKubesharkBinary(t)
cleanupKubeshark(t, binary)
session := startMCPSession(t, binary)
defer session.close()
session.initialize(t, 1)
resp := session.callTool(t, 2, "check_kubeshark_status", nil)
if resp.Error != nil {
t.Fatalf("check_kubeshark_status failed: %s", resp.Error.Message)
}
var result struct {
Content []struct{ Text string `json:"text"` } `json:"content"`
}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
if len(result.Content) == 0 || (!strings.Contains(result.Content[0].Text, "not running") && !strings.Contains(result.Content[0].Text, "NOT")) {
t.Errorf("Expected 'not running' status")
}
}
// TestMCP_StartKubeshark tests the start_kubeshark tool.
func TestMCP_StartKubeshark(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
requireKubernetesCluster(t)
binary := getKubesharkBinary(t)
cleanupKubeshark(t, binary)
t.Cleanup(func() { cleanupKubeshark(t, binary) })
session := startMCPSessionWithDestructive(t, binary)
defer session.close()
session.initialize(t, 1)
resp := session.callToolWithTimeout(t, 2, "start_kubeshark", nil, 3*time.Minute)
if resp.Error != nil {
t.Fatalf("start_kubeshark failed: %s", resp.Error.Message)
}
if !isKubesharkRunning(t) {
t.Error("Kubeshark should be running after start_kubeshark")
}
}
// TestMCP_StartKubeshark_WithoutFlag tests that start_kubeshark fails without --allow-destructive.
func TestMCP_StartKubeshark_WithoutFlag(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSession(t, getKubesharkBinary(t))
defer session.close()
session.initialize(t, 1)
resp := session.callTool(t, 2, "start_kubeshark", nil)
var result struct {
Content []struct{ Text string `json:"text"` } `json:"content"`
IsError bool `json:"isError"`
}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
if !result.IsError {
t.Error("Expected isError=true without --allow-destructive")
}
}
// TestMCP_StopKubeshark tests the stop_kubeshark tool.
func TestMCP_StopKubeshark(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
requireKubernetesCluster(t)
binary := getKubesharkBinary(t)
session := startMCPSessionWithDestructive(t, binary)
defer session.close()
session.initialize(t, 0)
// Start Kubeshark if not running
if !isKubesharkRunning(t) {
resp := session.callToolWithTimeout(t, 1, "start_kubeshark", nil, 2*time.Minute)
if resp.Error != nil {
t.Skipf("Could not start Kubeshark: %v", resp.Error.Message)
}
}
resp := session.callToolWithTimeout(t, 2, "stop_kubeshark", nil, 2*time.Minute)
if resp.Error != nil {
t.Fatalf("stop_kubeshark failed: %s", resp.Error.Message)
}
time.Sleep(5 * time.Second)
if isKubesharkRunning(t) {
t.Error("Kubeshark should not be running after stop_kubeshark")
}
}
// TestMCP_StopKubeshark_WithoutFlag tests that stop_kubeshark fails without --allow-destructive.
func TestMCP_StopKubeshark_WithoutFlag(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSession(t, getKubesharkBinary(t))
defer session.close()
session.initialize(t, 1)
resp := session.callTool(t, 2, "stop_kubeshark", nil)
var result struct {
IsError bool `json:"isError"`
}
if err := json.Unmarshal(resp.Result, &result); err != nil {
t.Fatalf("Failed to parse result: %v", err)
}
if !result.IsError {
t.Error("Expected isError=true without --allow-destructive")
}
}
// TestMCP_FullLifecycle tests the complete lifecycle: check -> start -> check -> stop -> check
func TestMCP_FullLifecycle(t *testing.T) {
if testing.Short() {
t.Skip("Skipping in short mode")
}
requireKubernetesCluster(t)
binary := getKubesharkBinary(t)
cleanupKubeshark(t, binary)
session := startMCPSessionWithDestructive(t, binary)
defer session.close()
session.initialize(t, 1)
// Check -> Start -> Check -> Stop -> Check
if resp := session.callTool(t, 2, "check_kubeshark_status", nil); resp.Error != nil {
t.Fatalf("Initial status check failed: %s", resp.Error.Message)
}
if resp := session.callToolWithTimeout(t, 3, "start_kubeshark", nil, 3*time.Minute); resp.Error != nil {
t.Fatalf("Start failed: %s", resp.Error.Message)
}
if err := waitForKubesharkReady(t, binary, startupTimeout); err != nil {
t.Fatalf("Kubeshark did not become ready: %v", err)
}
if resp := session.callTool(t, 4, "check_kubeshark_status", nil); resp.Error != nil {
t.Fatalf("Status check after start failed: %s", resp.Error.Message)
}
if resp := session.callToolWithTimeout(t, 5, "stop_kubeshark", nil, 2*time.Minute); resp.Error != nil {
t.Fatalf("Stop failed: %s", resp.Error.Message)
}
time.Sleep(5 * time.Second)
if resp := session.callTool(t, 6, "check_kubeshark_status", nil); resp.Error != nil {
t.Fatalf("Final status check failed: %s", resp.Error.Message)
}
}
// TestMCP_APIToolsRequireKubeshark tests that API tools return helpful errors when Kubeshark isn't running.
func TestMCP_APIToolsRequireKubeshark(t *testing.T) {
requireKubernetesCluster(t)
binary := getKubesharkBinary(t)
cleanupKubeshark(t, binary)
session := startMCPSession(t, binary)
defer session.close()
session.initialize(t, 1)
for i, tool := range []string{"list_workloads", "list_api_calls", "get_api_stats"} {
resp := session.callTool(t, i+2, tool, nil)
// Either error or helpful message is acceptable
if resp.Error != nil {
t.Logf("%s returned error (expected): %s", tool, resp.Error.Message)
}
}
}
// TestMCP_SetFlags tests that --set flags are passed correctly.
func TestMCP_SetFlags(t *testing.T) {
requireKubernetesCluster(t)
session := startMCPSession(t, getKubesharkBinary(t), "--set", "tap.namespaces={default}")
defer session.close()
session.initialize(t, 1)
resp := session.sendRequest(t, MCPRequest{JSONRPC: "2.0", ID: 2, Method: "tools/list"})
if resp.Error != nil {
t.Fatalf("tools/list failed with --set flags: %s", resp.Error.Message)
}
}
// BenchmarkMCP_CheckStatus benchmarks the check_kubeshark_status tool.
func BenchmarkMCP_CheckStatus(b *testing.B) {
if testing.Short() {
b.Skip("Skipping benchmark in short mode")
}
if !hasKubernetesCluster() {
b.Skip("Skipping: no Kubernetes cluster available")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cmd := exec.CommandContext(ctx, getKubesharkBinary(b), "mcp")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
reader := bufio.NewReader(stdout)
if err := cmd.Start(); err != nil {
b.Fatalf("Failed to start MCP: %v", err)
}
defer func() { cancel(); _ = cmd.Wait() }()
// Initialize
initReq, _ := json.Marshal(MCPRequest{
JSONRPC: "2.0", ID: 0, Method: "initialize",
Params: map[string]interface{}{
"protocolVersion": "2024-11-05",
"capabilities": map[string]interface{}{},
"clientInfo": map[string]interface{}{"name": "bench", "version": "1.0"},
},
})
_, _ = stdin.Write(append(initReq, '\n'))
_, _ = reader.ReadString('\n')
b.ResetTimer()
for i := 0; i < b.N; i++ {
req, _ := json.Marshal(MCPRequest{
JSONRPC: "2.0", ID: i + 1, Method: "tools/call",
Params: map[string]interface{}{"name": "check_kubeshark_status", "arguments": map[string]interface{}{}},
})
if _, err := stdin.Write(append(req, '\n')); err != nil {
b.Fatalf("Write failed: %v", err)
}
if _, err := reader.ReadString('\n'); err != nil {
b.Fatalf("Read failed: %v", err)
}
}
}

View File

@@ -8,5 +8,5 @@ const (
HubServiceName = HubPodName
K8sAllNamespaces = ""
MinKubernetesServerVersion = "1.16.0"
AppLabelKey = "app.kubeshark.co/app"
AppLabelKey = "app.kubeshark.com/app"
)

View File

@@ -67,7 +67,10 @@ func (h *Helm) Install() (rel *release.Release, err error) {
client.Namespace = h.releaseNamespace
client.ReleaseName = h.releaseName
chartPath := os.Getenv(fmt.Sprintf("%s_HELM_CHART_PATH", strings.ToUpper(misc.Program)))
chartPath := config.Config.Tap.Release.HelmChartPath
if chartPath == "" {
chartPath = os.Getenv(fmt.Sprintf("%s_HELM_CHART_PATH", strings.ToUpper(misc.Program)))
}
if chartPath == "" {
var chartURL string
chartURL, err = repo.FindChartInRepoURL(h.repo, h.releaseName, "", "", "", "", getter.All(&cli.EnvSettings{}))

View File

@@ -4,18 +4,17 @@ apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-hub-network-policy
namespace: default
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
policyTypes:
- Ingress
- Egress
@@ -34,10 +33,10 @@ apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-front-network-policy
@@ -45,7 +44,7 @@ metadata:
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
policyTypes:
- Ingress
- Egress
@@ -61,10 +60,37 @@ apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-dex-network-policy
namespace: default
spec:
podSelector:
matchLabels:
app.kubeshark.com/app: dex
policyTypes:
- Ingress
- Egress
ingress:
- ports:
- protocol: TCP
port: 5556
egress:
- {}
---
# Source: kubeshark/templates/17-network-policies.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-worker-network-policy
@@ -72,7 +98,7 @@ metadata:
spec:
podSelector:
matchLabels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
policyTypes:
- Ingress
- Egress
@@ -90,12 +116,11 @@ apiVersion: v1
kind: ServiceAccount
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-service-account
namespace: default
---
@@ -106,15 +131,17 @@ metadata:
name: kubeshark-secret
namespace: default
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
stringData:
LICENSE: ''
SCRIPTING_ENV: '{}'
OIDC_CLIENT_ID: 'not set'
OIDC_CLIENT_SECRET: 'not set'
---
# Source: kubeshark/templates/13-secret.yaml
kind: Secret
@@ -123,11 +150,11 @@ metadata:
name: kubeshark-saml-x509-crt-secret
namespace: default
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
stringData:
AUTH_SAML_X509_CRT: |
@@ -139,11 +166,11 @@ metadata:
name: kubeshark-saml-x509-key-secret
namespace: default
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
stringData:
AUTH_SAML_X509_KEY: |
@@ -155,10 +182,10 @@ metadata:
name: kubeshark-nginx-config-map
namespace: default
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
data:
default.conf: |
@@ -172,6 +199,10 @@ data:
client_header_buffer_size 32k;
large_client_header_buffers 8 64k;
proxy_buffer_size 64k;
proxy_buffers 4 128k;
proxy_busy_buffers_size 128k;
location /api {
rewrite ^/api(.*)$ $1 break;
proxy_pass http://kubeshark-hub;
@@ -182,8 +213,10 @@ data:
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
proxy_connect_timeout 4s;
proxy_read_timeout 120s;
proxy_send_timeout 12s;
# Disable buffering for gRPC/Connect streaming
client_max_body_size 0;
proxy_request_buffering off;
proxy_buffering off;
proxy_pass_request_headers on;
}
@@ -218,64 +251,73 @@ metadata:
name: kubeshark-config-map
namespace: default
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
data:
POD_REGEX: '.*'
NAMESPACES: ''
EXCLUDED_NAMESPACES: ''
BPF_OVERRIDE: ''
STOPPED: 'false'
DISSECTION_ENABLED: 'true'
CAPTURE_SELF: 'false'
SCRIPTING_SCRIPTS: '{}'
SCRIPTING_ACTIVE_SCRIPTS: ''
INGRESS_ENABLED: 'false'
INGRESS_HOST: 'ks.svc.cluster.local'
PROXY_FRONT_PORT: '8899'
AUTH_ENABLED: 'true'
AUTH_TYPE: 'oidc'
AUTH_TYPE: 'default'
AUTH_SAML_IDP_METADATA_URL: ''
AUTH_SAML_ROLE_ATTRIBUTE: 'role'
AUTH_SAML_ROLES: '{"admin":{"canDownloadPCAP":true,"canStopTrafficCapturing":true,"canUpdateTargetedPods":true,"canUseScripting":true,"filter":"","scriptingPermissions":{"canActivate":true,"canDelete":true,"canSave":true},"showAdminConsoleLink":true}}'
AUTH_ROLES: '{"admin":{"canControlDissection":true,"canDownloadPCAP":true,"canStopTrafficCapturing":true,"canUpdateTargetedPods":true,"canUseScripting":true,"filter":"","scriptingPermissions":{"canActivate":true,"canDelete":true,"canSave":true},"showAdminConsoleLink":true}}'
AUTH_ROLES_CLAIM: 'role'
AUTH_DEFAULT_ROLE: ''
AUTH_OIDC_ISSUER: 'not set'
AUTH_OIDC_REFRESH_TOKEN_LIFETIME: '3960h'
AUTH_OIDC_STATE_PARAM_EXPIRY: '10m'
AUTH_OIDC_BYPASS_SSL_CA_CHECK: 'false'
TELEMETRY_DISABLED: 'false'
SCRIPTING_DISABLED: ''
TARGETED_PODS_UPDATE_DISABLED: ''
SCRIPTING_DISABLED: 'false'
TARGETED_PODS_UPDATE_DISABLED: 'false'
PRESET_FILTERS_CHANGING_ENABLED: 'true'
RECORDING_DISABLED: ''
STOP_TRAFFIC_CAPTURING_DISABLED: 'false'
RECORDING_DISABLED: 'false'
DISSECTION_CONTROL_ENABLED: 'true'
GLOBAL_FILTER: ""
DEFAULT_FILTER: "!dns and !error"
DEFAULT_FILTER: ""
TRAFFIC_SAMPLE_RATE: '100'
JSON_TTL: '5m'
PCAP_TTL: '10s'
PCAP_ERROR_TTL: '60s'
PCAP_TTL: '0'
PCAP_ERROR_TTL: '0'
TIMEZONE: ' '
CLOUD_LICENSE_ENABLED: 'true'
DUPLICATE_TIMEFRAME: '200ms'
ENABLED_DISSECTORS: 'amqp,dns,http,icmp,kafka,redis,sctp,syscall,ws,ldap'
ENABLED_DISSECTORS: 'amqp,dns,http,icmp,kafka,mongodb,mysql,postgresql,redis,ws,tlsx,ldap,radius,diameter,udp-flow,tcp-flow,udp-conn,tcp-conn'
CUSTOM_MACROS: '{"https":"tls and (http or http2)"}'
DISSECTORS_UPDATING_ENABLED: 'true'
SNAPSHOTS_UPDATING_ENABLED: 'true'
DEMO_MODE_ENABLED: 'false'
DETECT_DUPLICATES: 'false'
PCAP_DUMP_ENABLE: 'true'
PCAP_DUMP_ENABLE: 'false'
PCAP_TIME_INTERVAL: '1m'
PCAP_MAX_TIME: '1h'
PCAP_MAX_SIZE: '500MB'
PCAP_SRC_DIR: 'pcapdump'
PORT_MAPPING: '{"amqp":[5671,5672],"diameter":[3868],"http":[80,443,8080],"kafka":[9092],"ldap":[389],"mongodb":[27017],"mysql":[3306],"postgresql":[5432],"redis":[6379]}'
RAW_CAPTURE_ENABLED: 'true'
RAW_CAPTURE_STORAGE_SIZE: '1Gi'
---
# Source: kubeshark/templates/02-cluster-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-cluster-role-default
namespace: default
rules:
@@ -312,18 +354,23 @@ rules:
- create
- update
- delete
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
# Source: kubeshark/templates/03-cluster-role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-cluster-role-binding-default
namespace: default
roleRef:
@@ -340,10 +387,10 @@ apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-self-config-role
@@ -355,25 +402,54 @@ rules:
resourceNames:
- kubeshark-secret
- kubeshark-config-map
- kubeshark-secret-default
- kubeshark-config-map-default
resources:
- secrets
- configmaps
verbs:
- create
- get
- watch
- list
- update
- patch
- delete
- apiGroups:
- ""
- v1
resources:
- secrets
- configmaps
- pods/log
verbs:
- create
- get
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- create
- get
- list
- delete
- apiGroups:
- batch
resources:
- jobs
verbs:
- "*"
---
# Source: kubeshark/templates/03-cluster-role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-self-config-role-binding
@@ -392,13 +468,12 @@ apiVersion: v1
kind: Service
metadata:
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-hub
namespace: default
spec:
@@ -407,7 +482,7 @@ spec:
port: 80
targetPort: 8080
selector:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
type: ClusterIP
---
# Source: kubeshark/templates/07-front-service.yaml
@@ -415,12 +490,11 @@ apiVersion: v1
kind: Service
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-front
namespace: default
spec:
@@ -429,7 +503,7 @@ spec:
port: 80
targetPort: 8080
selector:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
type: ClusterIP
---
# Source: kubeshark/templates/15-worker-service-metrics.yaml
@@ -437,10 +511,10 @@ kind: Service
apiVersion: v1
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
prometheus.io/scrape: 'true'
@@ -449,11 +523,11 @@ metadata:
namespace: default
spec:
selector:
app.kubeshark.co/app: worker
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: worker
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
ports:
- name: metrics
@@ -466,10 +540,10 @@ kind: Service
apiVersion: v1
metadata:
labels:
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
prometheus.io/scrape: 'true'
@@ -478,11 +552,11 @@ metadata:
namespace: default
spec:
selector:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
ports:
- name: metrics
@@ -495,30 +569,30 @@ apiVersion: apps/v1
kind: DaemonSet
metadata:
labels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
sidecar.istio.io/inject: "false"
helm.sh/chart: kubeshark-52.3.94
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-worker-daemon-set
namespace: default
spec:
selector:
matchLabels:
app.kubeshark.co/app: worker
app.kubeshark.com/app: worker
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
template:
metadata:
labels:
app.kubeshark.co/app: worker
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: worker
kubeshark.io/internal-auth: "true"
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
name: kubeshark-worker-daemon-set
namespace: kubeshark
@@ -528,26 +602,15 @@ spec:
- /bin/sh
- -c
- mkdir -p /sys/fs/bpf && mount | grep -q '/sys/fs/bpf' || mount -t bpf bpf /sys/fs/bpf
image: 'docker.io/kubeshark/worker:v52.3.94'
image: 'docker.io/kubeshark/worker:v53.3'
imagePullPolicy: Always
name: check-bpf
name: mount-bpf
securityContext:
privileged: true
volumeMounts:
- mountPath: /sys
name: sys
mountPropagation: Bidirectional
- command:
- ./tracer
- -init-bpf
image: 'docker.io/kubeshark/worker:v52.3.94'
imagePullPolicy: Always
name: init-bpf
securityContext:
privileged: true
volumeMounts:
- mountPath: /sys
name: sys
containers:
- command:
- ./worker
@@ -561,16 +624,24 @@ spec:
- 'best'
- -loglevel
- 'warning'
- -unixsocket
- -servicemesh
- -procfs
- /hostproc
- -disable-ebpf
- -resolution-strategy
- 'auto'
- -staletimeout
- '30'
image: 'docker.io/kubeshark/worker:v52.3.94'
- -tcp-flow-full-timeout
- '1200'
- -udp-flow-full-timeout
- '1200'
- -storage-size
- '10Gi'
- -capture-db-max-size
- '500Mi'
- -cloud-api-url
- 'https://api.kubeshark.com'
image: 'docker.io/kubeshark/worker:v53.3'
imagePullPolicy: Always
name: sniffer
ports:
@@ -586,12 +657,14 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: TCP_STREAM_CHANNEL_TIMEOUT_MS
value: '10000'
- name: TCP_STREAM_CHANNEL_TIMEOUT_SHOW
value: 'false'
- name: KUBESHARK_CLOUD_API_URL
value: 'https://api.kubeshark.co'
- name: PROFILING_ENABLED
value: 'false'
- name: SENTRY_ENABLED
@@ -612,28 +685,16 @@ spec:
memory: 50Mi
securityContext:
capabilities:
add:
- NET_RAW
- NET_ADMIN
- SYS_ADMIN
- SYS_PTRACE
- DAC_OVERRIDE
- SYS_ADMIN
- SYS_PTRACE
- SYS_RESOURCE
- IPC_LOCK
drop:
- ALL
privileged: true
readinessProbe:
periodSeconds: 1
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 5
tcpSocket:
port: 48999
livenessProbe:
periodSeconds: 1
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 5
@@ -646,17 +707,17 @@ spec:
- mountPath: /sys
name: sys
readOnly: true
mountPropagation: HostToContainer
- mountPath: /app/data
name: data
- command:
- ./tracer
- -procfs
- /hostproc
- -disable-ebpf
- -disable-tls-log
# - -loglevel
# - 'warning'
image: 'docker.io/kubeshark/worker:v52.3.94'
- -loglevel
- 'warning'
image: 'docker.io/kubeshark/worker:v53.3'
imagePullPolicy: Always
name: tracer
env:
@@ -668,6 +729,10 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: PROFILING_ENABLED
value: 'false'
- name: SENTRY_ENABLED
@@ -688,16 +753,7 @@ spec:
memory: 50Mi
securityContext:
capabilities:
add:
- SYS_ADMIN
- SYS_PTRACE
- SYS_RESOURCE
- IPC_LOCK
- NET_RAW
- NET_ADMIN
drop:
- ALL
privileged: true
volumeMounts:
- mountPath: /hostproc
name: proc
@@ -705,6 +761,7 @@ spec:
- mountPath: /sys
name: sys
readOnly: true
mountPropagation: HostToContainer
- mountPath: /app/data
name: data
- mountPath: /etc/os-release
@@ -717,12 +774,10 @@ spec:
dnsPolicy: ClusterFirstWithHostNet
hostNetwork: true
serviceAccountName: kubeshark-service-account
terminationGracePeriodSeconds: 0
tolerations:
- effect: NoExecute
operator: Exists
- effect: NoSchedule
operator: Exists
- key:
operator: "Exists"
effect: "NoExecute"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@@ -750,37 +805,36 @@ spec:
name: root
- name: data
emptyDir:
sizeLimit: 5000Mi
sizeLimit: 10Gi
---
# Source: kubeshark/templates/04-hub-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-hub
namespace: default
spec:
replicas: 1 # Set the desired number of replicas
selector:
matchLabels:
app.kubeshark.co/app: hub
app.kubeshark.com/app: hub
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
template:
metadata:
labels:
app.kubeshark.co/app: hub
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: hub
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
spec:
dnsPolicy: ClusterFirstWithHostNet
@@ -793,6 +847,20 @@ spec:
- "8080"
- -loglevel
- 'warning'
- -capture-stop-after
- "5m"
- -snapshot-size-limit
- '20Gi'
- -dissector-image
- 'docker.io/kubeshark/worker:v53.3'
- -dissector-cpu
- '1'
- -dissector-memory
- '4Gi'
- -dissector-storage-size
- '20Gi'
- -cloud-api-url
- 'https://api.kubeshark.com'
env:
- name: POD_NAME
valueFrom:
@@ -806,24 +874,22 @@ spec:
value: 'false'
- name: SENTRY_ENVIRONMENT
value: 'production'
- name: KUBESHARK_CLOUD_API_URL
value: 'https://api.kubeshark.co'
- name: PROFILING_ENABLED
value: 'false'
image: 'docker.io/kubeshark/hub:v52.3.94'
image: 'docker.io/kubeshark/hub:v53.3'
imagePullPolicy: Always
readinessProbe:
periodSeconds: 1
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
initialDelaySeconds: 5
tcpSocket:
port: 8080
livenessProbe:
periodSeconds: 1
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
initialDelaySeconds: 3
initialDelaySeconds: 5
tcpSocket:
port: 8080
resources:
@@ -843,6 +909,17 @@ spec:
- name: saml-x509-volume
mountPath: "/etc/saml/x509"
readOnly: true
- name: snapshots-volume
mountPath: "/app/data/snapshots"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
volumes:
- name: saml-x509-volume
projected:
@@ -857,36 +934,38 @@ spec:
items:
- key: AUTH_SAML_X509_KEY
path: kubeshark.key
- name: snapshots-volume
emptyDir:
sizeLimit: 20Gi
---
# Source: kubeshark/templates/06-front-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubeshark.co/app: front
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: front
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
annotations:
name: kubeshark-front
namespace: default
spec:
replicas: 1 # Set the desired number of replicas
selector:
matchLabels:
app.kubeshark.co/app: front
app.kubeshark.com/app: front
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
template:
metadata:
labels:
app.kubeshark.co/app: front
helm.sh/chart: kubeshark-52.3.94
app.kubeshark.com/app: front
helm.sh/chart: kubeshark-53.3.0
app.kubernetes.io/name: kubeshark
app.kubernetes.io/instance: kubeshark
app.kubernetes.io/version: "52.3.94"
app.kubernetes.io/version: "53.3.0"
app.kubernetes.io/managed-by: Helm
spec:
containers:
@@ -894,11 +973,17 @@ spec:
- name: REACT_APP_AUTH_ENABLED
value: 'true'
- name: REACT_APP_AUTH_TYPE
value: 'oidc'
value: 'default'
- name: REACT_APP_COMPLETE_STREAMING_ENABLED
value: 'true'
- name: REACT_APP_STREAMING_TYPE
value: 'connect-rpc'
- name: REACT_APP_AUTH_SAML_IDP_METADATA_URL
value: ' '
- name: REACT_APP_TIMEZONE
value: ' '
- name: REACT_APP_SCRIPTING_HIDDEN
value: 'true'
- name: REACT_APP_SCRIPTING_DISABLED
value: 'false'
- name: REACT_APP_TARGETED_PODS_UPDATE_DISABLED
@@ -906,22 +991,36 @@ spec:
- name: REACT_APP_PRESET_FILTERS_CHANGING_ENABLED
value: 'true'
- name: REACT_APP_BPF_OVERRIDE_DISABLED
value: 'false'
value: 'true'
- name: REACT_APP_RECORDING_DISABLED
value: 'false'
- name: REACT_APP_STOP_TRAFFIC_CAPTURING_DISABLED
value: 'false'
- name: REACT_APP_DISSECTION_ENABLED
value: 'true'
- name: REACT_APP_DISSECTION_CONTROL_ENABLED
value: 'true'
- name: 'REACT_APP_CLOUD_LICENSE_ENABLED'
value: 'true'
- name: REACT_APP_SUPPORT_CHAT_ENABLED
value: 'true'
value: 'false'
- name: REACT_APP_BETA_ENABLED
value: 'false'
- name: REACT_APP_DISSECTORS_UPDATING_ENABLED
value: 'true'
- name: REACT_APP_SNAPSHOTS_UPDATING_ENABLED
value: 'true'
- name: REACT_APP_DEMO_MODE_ENABLED
value: 'false'
- name: REACT_APP_CLUSTER_WIDE_MAP_ENABLED
value: 'false'
- name: REACT_APP_RAW_CAPTURE_ENABLED
value: 'true'
- name: REACT_APP_ENTRIES_LIMIT
value: '300000'
- name: REACT_APP_SENTRY_ENABLED
value: 'false'
- name: REACT_APP_SENTRY_ENVIRONMENT
value: 'production'
image: 'docker.io/kubeshark/front:v52.3.94'
image: 'docker.io/kubeshark/front:v53.3'
imagePullPolicy: Always
name: kubeshark-front
livenessProbe:
@@ -951,6 +1050,15 @@ spec:
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
readOnly: true
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
volumes:
- name: nginx-config
configMap:

View File

@@ -5,7 +5,7 @@ metadata:
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: info@kubeshark.co
email: info@kubeshark.com
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:

220
mcp/README.md Normal file
View File

@@ -0,0 +1,220 @@
# Kubeshark MCP Server
[Kubeshark](https://kubeshark.com) MCP (Model Context Protocol) server enables AI assistants like Claude Desktop, Cursor, and other MCP-compatible clients to query real-time Kubernetes network traffic.
## AI Skills
The MCP provides the tools — [AI skills](../skills/) teach agents how to use them.
Skills turn raw MCP capabilities into domain-specific workflows like root cause
analysis, traffic filtering, and forensic investigation. See the
[skills README](../skills/README.md) for installation and usage.
| Skill | Description |
|-------|-------------|
| [`network-rca`](../skills/network-rca/) | Network Root Cause Analysis — snapshot-based retrospective investigation with PCAP and dissection routes |
| [`kfl`](../skills/kfl/) | KFL2 filter expert — write, debug, and optimize traffic queries across all supported protocols |
## Features
- **L7 API Traffic Analysis**: Query HTTP, gRPC, Redis, Kafka, DNS transactions
- **L4 Network Flows**: View TCP/UDP flows with traffic statistics
- **Cluster Management**: Start/stop Kubeshark deployments (with safety controls)
- **PCAP Snapshots**: Create and export network captures
- **Built-in Prompts**: Pre-configured prompts for common analysis tasks
## Installation
### 1. Install Kubeshark CLI
```bash
# macOS
brew install kubeshark
# Linux
sh <(curl -Ls https://kubeshark.com/install)
# Windows (PowerShell)
choco install kubeshark
```
Or download from [GitHub Releases](https://github.com/kubeshark/kubeshark/releases).
### 2. Configure Claude Desktop
Add to your Claude Desktop configuration:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
#### Default (requires kubectl access / kube context)
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp"]
}
}
}
```
With an explicit kubeconfig path:
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp", "--kubeconfig", "/path/to/.kube/config"]
}
}
}
```
#### URL Mode (no kubectl required)
Use this when the machine doesn't have kubectl access or a kube context.
Connect directly to an existing Kubeshark deployment:
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp", "--url", "https://kubeshark.example.com"]
}
}
}
```
#### With Destructive Operations
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp", "--allow-destructive", "--kubeconfig", "/path/to/.kube/config"]
}
}
}
```
### 3. Generate Configuration
Use the CLI to generate configuration:
```bash
kubeshark mcp --mcp-config --url https://kubeshark.example.com
```
## Available Tools
### Traffic Analysis (All Modes)
| Tool | Description |
|------|-------------|
| `list_workloads` | List pods, services, namespaces with observed traffic |
| `list_api_calls` | Query L7 API transactions with KFL filtering |
| `get_api_call` | Get detailed info about a specific API call |
| `get_api_stats` | Get aggregated API statistics |
| `list_l4_flows` | List L4 (TCP/UDP) network flows |
| `get_l4_flow_summary` | Get L4 connectivity summary |
| `list_snapshots` | List all PCAP snapshots |
| `create_snapshot` | Create a new PCAP snapshot |
| `get_dissection_status` | Check L7 protocol parsing status |
| `enable_dissection` | Enable L7 protocol dissection |
| `disable_dissection` | Disable L7 protocol dissection |
### Cluster Management (Proxy Mode Only)
| Tool | Description | Requires |
|------|-------------|----------|
| `check_kubeshark_status` | Check if Kubeshark is running | - |
| `start_kubeshark` | Deploy Kubeshark to cluster | `--allow-destructive` |
| `stop_kubeshark` | Remove Kubeshark from cluster | `--allow-destructive` |
## Available Prompts
| Prompt | Description |
|--------|-------------|
| `analyze_traffic` | Analyze API traffic patterns and identify issues |
| `find_errors` | Find and summarize API errors and failures |
| `trace_request` | Trace a request path through microservices |
| `show_topology` | Show service communication topology |
| `latency_analysis` | Analyze latency patterns and identify slow endpoints |
| `security_audit` | Audit traffic for security concerns |
| `compare_traffic` | Compare traffic patterns between time periods |
| `debug_connection` | Debug connectivity issues between services |
## Example Conversations
```
User: Show me all HTTP 500 errors in the last hour
Claude: I'll query the API traffic for 500 errors.
[Calling list_api_calls with kfl="http and response.status == 500"]
Found 12 HTTP 500 errors:
1. POST /api/checkout -> payment-service (500)
Time: 10:23:45 | Latency: 2340ms
...
```
```
User: What services are communicating with the database?
Claude: Let me check the L4 flows to the database.
[Calling list_l4_flows with dst_filter="postgres"]
Found 5 services connecting to postgres:5432:
- orders-service: 456KB transferred
- users-service: 123KB transferred
...
```
## CLI Options
| Option | Description |
|--------|-------------|
| `--url` | Direct URL to Kubeshark Hub |
| `--kubeconfig` | Path to kubeconfig file |
| `--allow-destructive` | Enable start/stop operations |
| `--list-tools` | List available tools and exit |
| `--mcp-config` | Print Claude Desktop config JSON |
## KFL (Kubeshark Filter Language)
Query traffic using KFL syntax:
```
# HTTP requests to a specific path
http and request.path == "/api/users"
# Errors only
response.status >= 400
# Specific source pod
src.pod.name == "frontend-.*"
# Multiple conditions
http and src.namespace == "default" and response.status == 500
```
## MCP Registry
Kubeshark is published to the [MCP Registry](https://registry.modelcontextprotocol.io/) automatically on each release.
The `server.json` in this directory is a reference file. The actual registry metadata (version, SHA256 hashes) is auto-generated during the release workflow. See [`.github/workflows/release.yml`](../.github/workflows/release.yml) for details.
## Links
- [Documentation](https://docs.kubeshark.com/en/mcp)
- [GitHub](https://github.com/kubeshark/kubeshark)
- [Website](https://kubeshark.com)
- [MCP Registry](https://registry.modelcontextprotocol.io/)
## License
Apache-2.0

178
mcp/server.json Normal file
View File

@@ -0,0 +1,178 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.kubeshark/mcp",
"displayName": "Kubeshark",
"description": "Real-time Kubernetes network traffic visibility and API analysis for HTTP, gRPC, Redis, Kafka, DNS.",
"icon": "https://raw.githubusercontent.com/kubeshark/assets/refs/heads/master/logo/ico/icon.ico",
"repository": {
"url": "https://github.com/kubeshark/kubeshark",
"source": "github"
},
"homepage": "https://kubeshark.com",
"license": "Apache-2.0",
"version": "AUTO_GENERATED_AT_RELEASE",
"authors": [
{
"name": "Kubeshark",
"url": "https://kubeshark.com"
}
],
"categories": [
"kubernetes",
"networking",
"observability",
"debugging",
"security"
],
"_note": "version and packages.fileSha256 are auto-generated at release time by .github/workflows/release.yml",
"tags": ["kubernetes", "network", "traffic", "api", "http", "grpc", "kafka", "redis", "dns", "pcap", "wireshark", "tcpdump", "observability", "debugging", "microservices"],
"packages": [
{
"registryType": "mcpb",
"identifier": "https://github.com/kubeshark/kubeshark/releases/download/vX.Y.Z/kubeshark-mcp_darwin_arm64.mcpb",
"fileSha256": "AUTO_GENERATED",
"transport": { "type": "stdio" }
},
{
"registryType": "mcpb",
"identifier": "https://github.com/kubeshark/kubeshark/releases/download/vX.Y.Z/kubeshark-mcp_darwin_amd64.mcpb",
"fileSha256": "AUTO_GENERATED",
"transport": { "type": "stdio" }
},
{
"registryType": "mcpb",
"identifier": "https://github.com/kubeshark/kubeshark/releases/download/vX.Y.Z/kubeshark-mcp_linux_arm64.mcpb",
"fileSha256": "AUTO_GENERATED",
"transport": { "type": "stdio" }
},
{
"registryType": "mcpb",
"identifier": "https://github.com/kubeshark/kubeshark/releases/download/vX.Y.Z/kubeshark-mcp_linux_amd64.mcpb",
"fileSha256": "AUTO_GENERATED",
"transport": { "type": "stdio" }
},
{
"registryType": "mcpb",
"identifier": "https://github.com/kubeshark/kubeshark/releases/download/vX.Y.Z/kubeshark-mcp_windows_amd64.mcpb",
"fileSha256": "AUTO_GENERATED",
"transport": { "type": "stdio" }
}
],
"tools": [
{
"name": "check_kubeshark_status",
"description": "Check if Kubeshark is currently running in the cluster. Read-only operation.",
"mode": "proxy"
},
{
"name": "start_kubeshark",
"description": "Deploy Kubeshark to the Kubernetes cluster. Requires --allow-destructive flag.",
"mode": "proxy",
"destructive": true
},
{
"name": "stop_kubeshark",
"description": "Remove Kubeshark from the Kubernetes cluster. Requires --allow-destructive flag.",
"mode": "proxy",
"destructive": true
},
{
"name": "list_workloads",
"description": "List pods, services, namespaces, and nodes with observed L7 traffic.",
"mode": "all"
},
{
"name": "list_api_calls",
"description": "Query L7 API transactions (HTTP, gRPC, Redis, Kafka, DNS) with KFL filtering.",
"mode": "all"
},
{
"name": "get_api_call",
"description": "Get detailed information about a specific API call including headers and body.",
"mode": "all"
},
{
"name": "get_api_stats",
"description": "Get aggregated API statistics and metrics.",
"mode": "all"
},
{
"name": "list_l4_flows",
"description": "List L4 (TCP/UDP) network flows with traffic statistics.",
"mode": "all"
},
{
"name": "get_l4_flow_summary",
"description": "Get L4 connectivity summary including top talkers and cross-namespace traffic.",
"mode": "all"
},
{
"name": "list_snapshots",
"description": "List all PCAP snapshots.",
"mode": "all"
},
{
"name": "create_snapshot",
"description": "Create a new PCAP snapshot of captured traffic.",
"mode": "all"
},
{
"name": "get_dissection_status",
"description": "Check L7 protocol parsing status.",
"mode": "all"
},
{
"name": "enable_dissection",
"description": "Enable L7 protocol dissection.",
"mode": "all"
},
{
"name": "disable_dissection",
"description": "Disable L7 protocol dissection.",
"mode": "all"
}
],
"prompts": [
{ "name": "analyze_traffic", "description": "Analyze API traffic patterns and identify issues" },
{ "name": "find_errors", "description": "Find and summarize API errors and failures" },
{ "name": "trace_request", "description": "Trace a request path through microservices" },
{ "name": "show_topology", "description": "Show service communication topology" },
{ "name": "latency_analysis", "description": "Analyze latency patterns and identify slow endpoints" },
{ "name": "security_audit", "description": "Audit traffic for security concerns" },
{ "name": "compare_traffic", "description": "Compare traffic patterns between time periods" },
{ "name": "debug_connection", "description": "Debug connectivity issues between services" }
],
"configuration": {
"properties": {
"url": {
"type": "string",
"description": "Direct URL to Kubeshark Hub (e.g., https://kubeshark.example.com). When set, connects directly without kubectl/proxy.",
"examples": ["https://kubeshark.example.com", "http://localhost:8899"]
},
"kubeconfig": {
"type": "string",
"description": "Path to kubeconfig file for proxy mode.",
"examples": ["~/.kube/config", "/path/to/.kube/config"]
},
"allow-destructive": {
"type": "boolean",
"description": "Enable destructive operations (start_kubeshark, stop_kubeshark). Default: false for safety.",
"default": false
}
}
},
"modes": {
"url": {
"description": "Connect directly to an existing Kubeshark deployment via URL. Cluster management tools are disabled.",
"args": ["mcp", "--url", "${url}"]
},
"proxy": {
"description": "Connect via kubectl port-forward. Requires kubeconfig access to the cluster.",
"args": ["mcp", "--kubeconfig", "${kubeconfig}"]
},
"proxy-destructive": {
"description": "Proxy mode with destructive operations enabled.",
"args": ["mcp", "--kubeconfig", "${kubeconfig}", "--allow-destructive"]
}
}
}

View File

@@ -10,8 +10,8 @@ var (
Software = "Kubeshark"
Program = "kubeshark"
Description = "The API Traffic Analyzer for Kubernetes"
Website = "https://kubeshark.co"
Email = "info@kubeshark.co"
Website = "https://kubeshark.com"
Email = "support@kubeshark.com"
Ver = "0.0.0"
Branch = "master"
GitCommitHash = "" // this var is overridden using ldflags in makefile when building

121
skills/README.md Normal file
View File

@@ -0,0 +1,121 @@
# Kubeshark AI Skills
Open-source AI skills that work with the [Kubeshark MCP](https://github.com/kubeshark/kubeshark).
Skills teach AI agents how to use Kubeshark's MCP tools for specific workflows
like root cause analysis, traffic filtering, and forensic investigation.
Skills use the open [Agent Skills](https://github.com/anthropics/skills) format
and work with Claude Code, OpenAI Codex CLI, Gemini CLI, Cursor, and other
compatible agents.
## Available Skills
| Skill | Description |
|-------|-------------|
| [`network-rca`](network-rca/) | Network Root Cause Analysis. Retrospective traffic analysis via snapshots, with two investigation routes: PCAP (for Wireshark/compliance) and Dissection (for AI-driven API-level investigation). |
| [`kfl`](kfl/) | KFL2 (Kubeshark Filter Language) expert. Complete reference for writing, debugging, and optimizing CEL-based traffic filters across all supported protocols. |
| [`security-audit`](security-audit/) | Network Security Audit. Systematic 8-phase threat detection across MITRE ATT&CK tactics — C2, exfiltration, lateral movement, credential theft, cryptomining, protocol abuse — using snapshot-based traffic analysis. |
## Prerequisites
All skills require the Kubeshark MCP:
```bash
# Claude Code
claude mcp add kubeshark -- kubeshark mcp
# Without kubectl access (direct URL)
claude mcp add kubeshark -- kubeshark mcp --url https://kubeshark.example.com
```
For Claude Desktop, add to `claude_desktop_config.json`:
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp"]
}
}
}
```
## Installation
### Option 1: Plugin (recommended)
Install as a Claude Code plugin directly from GitHub:
```
/plugin marketplace add kubeshark/kubeshark
/plugin install kubeshark
```
Skills appear as `/kubeshark:network-rca` and `/kubeshark:kfl`. The plugin
also bundles the Kubeshark MCP configuration automatically.
### Option 2: Clone and run
```bash
git clone https://github.com/kubeshark/kubeshark
cd kubeshark
claude
```
Skills trigger automatically based on your conversation.
### Option 3: Manual installation
Clone the repo (if you haven't already), then symlink or copy the skills:
```bash
git clone https://github.com/kubeshark/kubeshark
mkdir -p ~/.claude/skills
# Symlink to stay in sync with the repo (recommended)
ln -s kubeshark/skills/network-rca ~/.claude/skills/network-rca
ln -s kubeshark/skills/kfl ~/.claude/skills/kfl
# Or copy to your project (project scope only)
mkdir -p .claude/skills
cp -r kubeshark/skills/network-rca .claude/skills/
cp -r kubeshark/skills/kfl .claude/skills/
# Or copy for personal use (all your projects)
cp -r kubeshark/skills/network-rca ~/.claude/skills/
cp -r kubeshark/skills/kfl ~/.claude/skills/
```
## Contributing
We welcome contributions — whether improving an existing skill or proposing a new one.
- **Suggest improvements**: Open an issue or PR with changes to an existing skill's `SKILL.md`
or reference docs. Better examples, clearer workflows, and additional filter patterns
are always appreciated.
- **Add a new skill**: Open an issue describing the use case first. New skills should
follow the structure below and reference Kubeshark MCP tools by exact name.
### Skill structure
```
skills/
└── <skill-name>/
├── SKILL.md # Required. YAML frontmatter + markdown body.
└── references/ # Optional. Detailed reference docs.
└── *.md
```
### Guidelines
- Keep `SKILL.md` under 500 lines. Use `references/` for detailed content.
- Use imperative tone. Reference MCP tools by exact name.
- Include realistic example tool responses.
- The `description` frontmatter should be generous with trigger keywords.
### Planned skills
- `api-security` — OWASP API Top 10 assessment against live or snapshot traffic.
- `incident-response` — 7-phase forensic incident investigation methodology.
- `network-engineering` — Real-time traffic analysis, latency debugging, dependency mapping.

489
skills/install/SKILL.md Normal file
View File

@@ -0,0 +1,489 @@
---
name: install
user-invocable: true
description: >
Kubeshark installation and deployment skill. Use this skill whenever the user wants
to install Kubeshark, deploy Kubeshark to a Kubernetes cluster, set up Kubeshark,
configure Kubeshark helm values, generate a Kubeshark config file, customize
Kubeshark deployment, troubleshoot Kubeshark installation, upgrade Kubeshark,
uninstall Kubeshark, or manage the Kubeshark Helm release. Also trigger when
the user mentions "kubeshark tap", "kubeshark clean", "helm install kubeshark",
"get kubeshark running", "set up traffic capture", "deploy kubeshark",
"kubeshark not starting", "kubeshark pods not ready", "configure namespaces",
"persistent storage", "cloud storage for snapshots", "kubeshark ingress",
"kubeshark auth", "kubeshark SAML", "kubeshark license", "kubeshark config",
"custom helm values", "kubeshark on EKS/GKE/AKS", "kubeshark on OpenShift",
"kubeshark on KinD/minikube/k3s", "air-gapped", "offline install",
or any request related to getting Kubeshark installed, configured, and running
in a Kubernetes cluster.
---
# Kubeshark Installation & Deployment
You are a Kubeshark deployment specialist. Your job is to help users install,
configure, and deploy Kubeshark to their Kubernetes cluster — tailoring the
configuration to their specific environment, requirements, and use case.
Kubeshark deploys via Helm. The CLI (`kubeshark tap`) is a thin wrapper that
installs a basic Helm chart and establishes a port-forward — nothing more.
For larger or production clusters, use Helm directly with a custom values file.
## Decision: CLI or Helm?
**Use the CLI** when:
- Quick install on a dev/test cluster (minikube, KinD, k3s)
- Personal environment, single user
- Just want to try Kubeshark quickly
**Use Helm directly** when:
- Larger cluster (staging, production)
- Need custom configuration (ingress, auth, storage, namespaces)
- GitOps / infrastructure-as-code workflows
- Team environment
## Path A: CLI (Dev/Test Clusters)
### Step 1 — Install the CLI
Check if Kubeshark is already installed:
```bash
kubeshark version
```
If not installed, offer one of these methods:
**Homebrew (easiest, where available):**
```bash
brew tap kubeshark/kubeshark
brew install kubeshark
```
**Binary download:**
For the full list of platforms and architectures, see https://docs.kubeshark.com/en/install
```bash
# Linux (amd64)
curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/latest/download/kubeshark_linux_amd64
chmod +x kubeshark
sudo mv kubeshark /usr/local/bin/
# Linux (arm64)
curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/latest/download/kubeshark_linux_arm64
chmod +x kubeshark
sudo mv kubeshark /usr/local/bin/
# macOS (Apple Silicon)
curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/latest/download/kubeshark_darwin_arm64
chmod +x kubeshark
sudo mv kubeshark /usr/local/bin/
# macOS (Intel)
curl -Lo kubeshark https://github.com/kubeshark/kubeshark/releases/latest/download/kubeshark_darwin_amd64
chmod +x kubeshark
sudo mv kubeshark /usr/local/bin/
```
### Step 2 — Check for Updates
**Always check for updates before using the CLI.** This is critical — Kubeshark
releases frequently and running an outdated version can cause issues.
```bash
# Homebrew
brew upgrade kubeshark
# Binary — check the latest release and re-download if newer
kubeshark version
# Compare with https://github.com/kubeshark/kubeshark/releases/latest
```
### Step 3 — Deploy with `kubeshark tap`
```bash
kubeshark tap
```
This installs the Helm chart with defaults and opens the dashboard in your browser.
That's it for dev/test clusters.
### Step 4 — Reconnect if Connection Breaks
If the port-forward drops (laptop sleep, network change, terminal closed):
```bash
kubeshark proxy
```
This re-establishes the port-forward and reopens the dashboard. It does **not**
reinstall — Kubeshark is still running in the cluster.
### Step 5 — Clean Up After Use
**Always clean up when done.** Kubeshark runs eBPF probes and DaemonSet workers
on every node — leaving it running wastes cluster resources.
```bash
kubeshark clean
```
Always remind the user to run `kubeshark clean` when they're finished. This is
easy to forget and important.
## Path B: Helm (Larger / Production Clusters)
### Step 1 — Upgrade the Helm Chart
**Always update the Helm repo first.** This is the most important first step —
running an outdated chart can cause issues.
```bash
helm repo add kubeshark https://helm.kubeshark.com
helm repo update
```
### Step 2 — Create a Config Directory
Store all configuration files in `~/.kubeshark/`:
```bash
mkdir -p ~/.kubeshark
```
**Before writing any file to `~/.kubeshark/`, check if it already exists.**
If `~/.kubeshark/values.yaml` (or any target filename) already exists, **ask the
user** before overwriting. Either:
1. Back up the existing file first: `cp ~/.kubeshark/values.yaml ~/.kubeshark/values.yaml.bak.$(date +%s)`
2. Use a descriptive name for the new file (e.g., `values-production.yaml`, `values-staging.yaml`)
The user may have multiple values files for different clusters or environments.
### Step 3 — Build the Values File
Walk through the following configuration areas with the user. Each section
explains what the value does and what to recommend.
#### Pod Targeting (CRITICAL)
```yaml
tap:
regex: .*
namespaces: []
excludedNamespaces: []
```
**This is one of the most important configuration decisions.** By default,
Kubeshark monitors the entire cluster's traffic. On a large cluster this is a
huge undertaking that consumes significant CPU and memory on every node.
**Always set namespace targeting.** Ask the user which namespaces contain the
workloads they care about, and set those explicitly:
```yaml
tap:
namespaces:
- production
- staging
```
Alternatively, use `excludedNamespaces` to monitor everything except specific
namespaces:
```yaml
tap:
excludedNamespaces:
- kube-system
- monitoring
- kubeshark
```
The `regex` field filters by pod name within the targeted namespaces. Leave as
`.*` unless the user wants to focus on specific pods.
Setting pod targeting rules causes Kubeshark to focus only on specific workloads,
which moderates compute consumption significantly.
#### Docker Registry (Air-Gapped Environments)
```yaml
tap:
docker:
registry: docker.io/kubeshark
tag: ""
```
- `tap.docker.registry` — Change this for air-gapped environments where there's
no access to `docker.io`. Point to your internal registry. Additional config
may be needed (pull secrets, registry credentials).
- `tap.docker.tag` — Set a specific version. If a patch version is missing, the
latest patch in that minor version is used. **Leave empty (recommended)** to
use the version matching the Helm chart.
For air-gapped clusters, also set:
```yaml
internetConnectivity: false
```
This is the **most important setting for air-gapped clusters** — it disables all
outbound connectivity checks (license validation, telemetry, update checks).
#### Capture & Dissection
```yaml
tap:
capture:
dissection:
enabled: true
stopAfter: 5m
raw:
enabled: true
storageSize: 1Gi
dbMaxSize: 500Mi
```
**`tap.capture.dissection.enabled`** — Controls real-time dissection (L7 protocol
parsing on production nodes). Real-time dissection consumes significant compute
resources from production nodes. **Recommend starting with `false` (disabled).**
This can be toggled on-demand from the dashboard when needed, so it's used only
when necessary and doesn't consume resources the rest of the time.
Dissection is independent from raw capture + snapshots. Raw capture is lightweight
and runs continuously; dissection is the heavy operation.
**`tap.capture.dissection.stopAfter`** — Time after which dissection automatically
disables once all client connections end. Set to `0` to never auto-disable (manual
control only).
**`tap.capture.raw.enabled`** — Keep this `true`. Raw capture consumes very little
production resources yet captures all traffic. This is what powers snapshots and
retrospective analysis.
**`tap.capture.raw.storageSize`** — The FIFO buffer for raw capture per node.
**Recommend 100Gi** for production. The larger this is, the further back in time
snapshots can reach.
**`tap.capture.dbMaxSize`** — Size of the database holding dissected API calls.
Bigger = more history kept. Adjust based on how much queryable history the user needs.
**`tap.capture.captureSelf`** — Debug option. Ignore during installation.
**`bpfOverride`** — Debug option. Ignore during installation.
#### Delayed Dissection
```yaml
tap:
delayedDissection:
cpu: "1"
memory: 4Gi
```
Delayed dissection is the process on the Hub that dissects raw capture data within
a snapshot. It runs on the Hub node (not production nodes) and is triggered when
a delayed dissection operation is requested on a snapshot.
**Give this as much resources as possible.** Recommend `cpu: "5"` and `memory: 5Gi`.
This speeds up snapshot analysis significantly.
#### Snapshot Storage (Local)
```yaml
tap:
snapshots:
local:
storageClass: ""
storageSize: 20Gi
```
This is where snapshots are stored locally. **Be very generous with this.**
**Recommend 2Ti (2TB)** for production environments that will accumulate snapshots.
**`storageClass`** — Must match a valid storage class in the cluster. Suggest
based on the cloud provider:
| Provider | Recommended Storage Class |
|----------|-------------------------|
| EKS (AWS) | `gp2` or `gp3` |
| GKE (Google) | `standard` or `premium-rwo` |
| AKS (Azure) | `managed-csi` or `managed-premium` |
| OpenShift | Check `kubectl get sc` — varies by provider |
| KinD / minikube | `standard` (default) |
| Private / bare metal | Ask the user for their storage class |
Always verify available storage classes with `kubectl get sc`.
#### Cloud Storage (Long-Term Retention)
Cloud storage enables uploading snapshots to S3, GCS, or Azure Blob for long-term
retention, cross-cluster sharing, and backup/restore.
For detailed configuration per provider (including IRSA, Workload Identity, static
credentials, and ConfigMap/Secret setup), see `references/cloud-storage.md`.
Summary of provider values:
```yaml
tap:
snapshots:
cloud:
provider: "" # "s3", "azblob", or "gcs" (empty = disabled)
prefix: "" # Key prefix in bucket
configMaps: [] # Pre-existing ConfigMaps with cloud config
secrets: [] # Pre-existing Secrets with cloud credentials
```
Help the user select the right provider based on where their cluster runs and
walk them through the authentication setup.
#### Resources
For a first installation, **do not change the resource defaults.** Let the user
run Kubeshark with defaults first and tune based on actual usage patterns later.
The defaults are reasonable starting points. Resource consumption depends heavily
on how much traffic is processed, which is controlled by pod targeting rules.
#### Node Selectors
```yaml
tap:
nodeSelectorTerms:
workers:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values: [linux]
```
Use `nodeSelectorTerms` when the user wants to focus on specific nodes. The less
workload processed by Kubeshark, the less CPU and memory it consumes. The goal is
to process workloads of interest, not the entire cluster.
#### Ingress (STRONGLY RECOMMENDED)
```yaml
tap:
ingress:
enabled: false
className: ""
host: ks.svc.cluster.local
path: /
tls: []
annotations: {}
```
**Ingress is the strongly preferred access method.** While port-forward is available,
it is **highly NOT recommended** for anything beyond quick local testing. Port-forward
is fragile, drops connections, and doesn't scale for team use.
**Always help the user configure ingress.** Ask them about their ingress controller
(nginx, ALB, Traefik, etc.) and build the ingress config:
```yaml
tap:
ingress:
enabled: true
className: nginx
host: kubeshark.example.com
tls:
- secretName: kubeshark-tls
hosts:
- kubeshark.example.com
annotations: {}
```
For ALB on AWS:
```yaml
tap:
ingress:
enabled: true
className: alb
host: kubeshark.example.com
annotations:
alb.ingress.kubernetes.io/scheme: internal
alb.ingress.kubernetes.io/target-type: ip
```
#### Air-Gapped Clusters
For air-gapped environments, two settings are essential:
```yaml
tap:
docker:
registry: your-internal-registry.example.com/kubeshark
internetConnectivity: false
```
`internetConnectivity: false` is the **single most important option** for
air-gapped clusters. Without it, Kubeshark will attempt outbound connections
that will fail and cause issues.
### Step 4 — Install
```bash
helm install kubeshark kubeshark/kubeshark \
-f ~/.kubeshark/values.yaml \
-n kubeshark --create-namespace
```
### Step 5 — Upgrade
When upgrading, **always update the Helm repo first**:
```bash
helm repo update
helm upgrade kubeshark kubeshark/kubeshark \
-f ~/.kubeshark/values.yaml \
-n kubeshark
```
## Uninstalling
**Via CLI:**
```bash
kubeshark clean
kubeshark clean -s kubeshark # Specific namespace
```
**Via Helm:**
```bash
helm uninstall kubeshark -n kubeshark
```
PersistentVolumeClaims are not deleted by default. Remove manually if needed:
```bash
kubectl delete pvc -l app.kubernetes.io/name=kubeshark -n kubeshark
```
## Troubleshooting
- **Pods not starting**: Check `kubectl get pods -l app.kubernetes.io/name=kubeshark -n <ns>`
and `kubectl describe pod`. Common: ImagePullBackOff (registry), Pending (storage/resources),
CrashLoopBackOff (check `kubectl logs`).
- **No traffic**: Verify namespaces have running pods, check pod regex, ensure eBPF supported
(kernel 4.14+, 5.4+ recommended).
- **Permissions**: Requires privileged containers with NET_RAW, NET_ADMIN, SYS_ADMIN,
SYS_PTRACE, SYS_RESOURCE, IPC_LOCK capabilities.
- **Storage**: Verify storage class exists (`kubectl get sc`), PVC is bound (`kubectl get pvc`).
## Setup Reference
### Kubeshark MCP for AI Agents
After installation, connect the Kubeshark MCP so AI agents can interact with Kubeshark:
```bash
# Claude Code
claude mcp add kubeshark -- kubeshark mcp
# Direct URL (no kubectl needed)
claude mcp add kubeshark -- kubeshark mcp --url https://kubeshark.example.com
```

View File

@@ -0,0 +1,96 @@
# Cloud Storage for Snapshots
This is a pointer to the authoritative cloud storage documentation maintained in
the Helm chart:
**Source of truth**: `helm-chart/docs/snapshots_cloud_storage.md`
Always read that file for the latest configuration details, including:
- Amazon S3 (static credentials, IRSA, cross-account AssumeRole)
- Azure Blob Storage (storage key, Workload Identity / DefaultAzureCredential)
- Google Cloud Storage (service account JSON, GKE Workload Identity)
- IAM permissions and trust policy examples
- ConfigMap and Secret setup patterns
- Inline values vs. external ConfigMap/Secret approaches
## Quick Reference
### Helm Values Structure
```yaml
tap:
snapshots:
cloud:
provider: "" # "s3", "azblob", or "gcs" (empty = disabled)
prefix: "" # Key prefix in the bucket/container
configMaps: [] # Pre-existing ConfigMaps with cloud config env vars
secrets: [] # Pre-existing Secrets with cloud credentials
s3:
bucket: ""
region: ""
accessKey: ""
secretKey: ""
roleArn: ""
externalId: ""
azblob:
storageAccount: ""
container: ""
storageKey: ""
gcs:
bucket: ""
project: ""
credentialsJson: ""
```
### Recommended Auth Per Provider
| Provider | Production Recommendation |
|----------|-------------------------|
| S3 (EKS) | IRSA (IAM Roles for Service Accounts) — no static credentials |
| S3 (non-EKS) | Static credentials via Secret, or default AWS credential chain |
| Azure Blob (AKS) | Workload Identity / Managed Identity |
| Azure Blob (non-AKS) | Storage account key via Secret |
| GCS (GKE) | GKE Workload Identity — no JSON key file |
| GCS (non-GKE) | Service account JSON key via Secret |
### Inline Values (Simplest Approach)
Set credentials directly in values.yaml. The Helm chart creates the necessary
ConfigMap/Secret resources automatically.
**S3:**
```yaml
tap:
snapshots:
cloud:
provider: "s3"
s3:
bucket: my-kubeshark-snapshots
region: us-east-1
```
**GCS:**
```yaml
tap:
snapshots:
cloud:
provider: "gcs"
gcs:
bucket: my-kubeshark-snapshots
project: my-gcp-project
```
**Azure Blob:**
```yaml
tap:
snapshots:
cloud:
provider: "azblob"
azblob:
storageAccount: mykubesharksa
container: snapshots
```
For production setups with proper IAM integration, see the full documentation
in `helm-chart/docs/snapshots_cloud_storage.md`.

View File

@@ -0,0 +1,376 @@
# Kubeshark Helm Values Reference
Complete reference for all Kubeshark Helm chart values. Use this when building
custom `values.yaml` files or `--set` flags.
## Docker Images
```yaml
tap:
docker:
registry: docker.io/kubeshark # Docker registry
tag: "" # Image tag (empty = chart appVersion)
tagLocked: true # Lock to specific tag
imagePullPolicy: Always # Always, IfNotPresent, Never
imagePullSecrets: [] # Registry pull secrets
overrideImage: # Override individual component images
worker: ""
hub: ""
front: ""
overrideTag: # Override individual component tags
worker: ""
hub: ""
front: ""
```
## Proxy / Port-Forward
```yaml
tap:
proxy:
worker:
srvPort: 48999
hub:
srvPort: 8898
front:
port: 8899 # Local port for port-forward
host: 127.0.0.1 # Bind address
```
## Pod Targeting
```yaml
tap:
regex: .* # Pod name regex filter
namespaces: [] # Target namespaces (empty = all)
excludedNamespaces: [] # Namespaces to exclude
bpfOverride: "" # Custom BPF filter override
```
## Capture & Dissection
```yaml
tap:
capture:
dissection:
enabled: true # Enable L7 dissection
stopAfter: 5m # Auto-stop dissection after duration
captureSelf: false # Capture Kubeshark's own traffic
raw:
enabled: true # Enable raw packet capture (needed for snapshots)
storageSize: 1Gi # FIFO buffer size per node
dbMaxSize: 500Mi # Max L7 database size per node
delayedDissection:
cpu: "1" # CPU for delayed dissection jobs
memory: 4Gi # Memory for delayed dissection jobs
storageSize: "" # Storage for delayed dissection
storageClass: "" # Storage class for delayed dissection
```
## Snapshots
```yaml
tap:
snapshots:
local:
storageClass: "" # Storage class for local snapshots
storageSize: 20Gi # PVC size for local snapshots
cloud:
provider: "" # s3, gcs, or azblob
prefix: "" # Path prefix in bucket
configMaps: [] # Additional ConfigMaps to mount
secrets: [] # Additional Secrets to mount
s3:
bucket: ""
region: ""
accessKey: ""
secretKey: ""
roleArn: "" # IAM role ARN (IRSA)
externalId: "" # STS external ID
azblob:
storageAccount: ""
container: ""
storageKey: ""
gcs:
bucket: ""
project: ""
credentialsJson: "" # Service account JSON
```
## Helm Release
```yaml
tap:
release:
repo: https://helm.kubeshark.com # Helm chart repository
name: kubeshark # Release name
namespace: default # Release namespace
helmChartPath: "" # Path to local chart (overrides repo)
```
## Storage
```yaml
tap:
persistentStorage: false # Enable PVC for worker data
persistentStorageStatic: false # Static provisioning
persistentStoragePvcVolumeMode: FileSystem # FileSystem or Block
efsFileSytemIdAndPath: "" # EFS file system ID (EKS)
secrets: [] # Additional secrets to mount
storageLimit: 10Gi # Max storage per node
storageClass: standard # Default storage class
```
## Resources
```yaml
tap:
resources:
hub:
limits:
cpu: "0" # 0 = no limit
memory: 5Gi
requests:
cpu: 50m
memory: 50Mi
sniffer:
limits:
cpu: "0"
memory: 5Gi
requests:
cpu: 50m
memory: 50Mi
tracer:
limits:
cpu: "0"
memory: 5Gi
requests:
cpu: 50m
memory: 50Mi
```
## Health Probes
```yaml
tap:
probes:
hub:
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
sniffer:
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
failureThreshold: 3
```
## TLS & Service Mesh
```yaml
tap:
serviceMesh: true # Capture mTLS traffic (service mesh)
tls: true # Capture OpenSSL/Go TLS traffic
disableTlsLog: true # Suppress TLS debug logging
packetCapture: best # Capture method: best, af_packet, pcap
```
## Labels, Annotations & Scheduling
```yaml
tap:
labels: {} # Additional labels for all pods
annotations: {} # Additional annotations for all pods
nodeSelectorTerms:
hub: # Hub pod node selector
- matchExpressions:
- key: kubernetes.io/os
operator: In
values: [linux]
workers: # Worker DaemonSet node selector
- matchExpressions:
- key: kubernetes.io/os
operator: In
values: [linux]
front: # Frontend pod node selector
- matchExpressions:
- key: kubernetes.io/os
operator: In
values: [linux]
tolerations:
hub: []
workers:
- operator: Exists
effect: NoExecute # Workers tolerate NoExecute by default
front: []
priorityClass: "" # PriorityClassName for pods
```
## Authentication
```yaml
tap:
auth:
enabled: false
type: saml # Only SAML supported currently
roles:
admin:
filter: "" # KFL filter restricting visible traffic
canDownloadPCAP: true
canUseScripting: true
scriptingPermissions:
canSave: true
canActivate: true
canDelete: true
canUpdateTargetedPods: true
canStopTrafficCapturing: true
canControlDissection: true
showAdminConsoleLink: true
rolesClaim: role # SAML attribute for role mapping
defaultRole: "" # Role for users without a role claim
defaultFilter: "" # Default KFL filter for all users
saml:
idpMetadataUrl: "" # SAML IdP metadata URL
x509crt: "" # SP certificate
x509key: "" # SP private key
```
## Ingress
```yaml
tap:
ingress:
enabled: false
className: "" # nginx, alb, traefik, etc.
host: ks.svc.cluster.local
path: /
tls: [] # TLS configuration
annotations: {} # Ingress annotations
```
## Protocol Dissectors
```yaml
tap:
enabledDissectors:
- amqp
- dns
- http
- icmp
- kafka
- mongodb
- mysql
- postgresql
- redis
- ws
- ldap
- radius
- diameter
- udp-flow
- tcp-flow
- udp-conn
- tcp-conn
portMapping: # Default port-to-protocol mappings
http: [80, 443, 8080]
amqp: [5671, 5672]
kafka: [9092]
mongodb: [27017]
mysql: [3306]
postgresql: [5432]
redis: [6379]
ldap: [389]
diameter: [3868]
customMacros:
https: "tls and (http or http2)"
```
## Networking & Security
```yaml
tap:
hostNetwork: true # Use host network (required for capture)
ipv6: true # Enable IPv6 support
mountBpf: true # Mount BPF filesystem
securityContext:
privileged: true
appArmorProfile:
type: ""
localhostProfile: ""
seLinuxOptions:
level: ""
role: ""
type: ""
user: ""
capabilities:
networkCapture: [NET_RAW, NET_ADMIN]
serviceMeshCapture: [SYS_ADMIN, SYS_PTRACE, DAC_OVERRIDE]
ebpfCapture: [SYS_ADMIN, SYS_PTRACE, SYS_RESOURCE, IPC_LOCK]
```
## Dashboard
```yaml
tap:
dashboard:
streamingType: connect-rpc
completeStreamingEnabled: true
clusterWideMapEnabled: false
entriesLimit: "300000"
routing:
front:
basePath: "" # Base path for reverse proxy
```
## Scripting
```yaml
scripting:
enabled: false
env: {} # Environment variables for scripts
source: "" # Git repo for scripts
sources: [] # Multiple script sources
watchScripts: true # Watch for script changes
active: [] # Active scripts
console: true # Enable script console
```
## Misc
```yaml
tap:
dryRun: false # Preview targeted pods without deploying
debug: false # Enable debug mode
telemetry:
enabled: true # Anonymous usage telemetry
resourceGuard:
enabled: false # Resource usage guard
watchdog:
enabled: false # Watchdog process
gitops:
enabled: false # GitOps mode
defaultFilter: "" # Default KFL display filter
globalFilter: "" # Global KFL filter (cannot be overridden)
dns:
nameservers: [] # Custom DNS nameservers
searches: [] # Custom DNS search domains
options: [] # Custom DNS options
misc:
jsonTTL: 5m # TTL for JSON entries
pcapTTL: "0" # TTL for PCAP files (0 = no TTL)
trafficSampleRate: 100 # Traffic sampling rate (1-100)
resolutionStrategy: auto # IP resolution: auto, dns, k8s
detectDuplicates: false # Detect duplicate packets
staleTimeoutSeconds: 30 # Timeout for stale connections
tcpFlowTimeout: 1200 # TCP flow idle timeout (seconds)
udpFlowTimeout: 1200 # UDP flow idle timeout (seconds)
headless: false # Suppress browser auto-open
license: "" # Kubeshark Pro license key
timezone: "" # Override timezone
logLevel: warning # Log level: debug, info, warning, error
kube:
configPath: "" # Custom kubeconfig path
context: "" # Kubernetes context name
```

401
skills/kfl/SKILL.md Normal file
View File

@@ -0,0 +1,401 @@
---
name: kfl
user-invocable: false
description: >
KFL2 (Kubeshark Filter Language) reference. This skill MUST be loaded before
writing, constructing, or suggesting any KFL filter expression. KFL is statically
typed — incorrect field names or syntax will fail silently or error. Do not guess
at KFL syntax without this skill loaded. Trigger on any mention of KFL, CEL filters,
traffic filtering, display filters, query syntax, filter expressions, write a filter,
construct a query, build a KFL, create a filter expression, "how do I filter",
"show me only", "find traffic where", protocol-specific queries (HTTP status codes,
DNS lookups, Redis commands, Kafka topics), Kubernetes-aware filtering (by namespace,
pod, service, label, annotation), L4 connection/flow filters, time-based queries,
or any request to slice/search/narrow network traffic in Kubeshark. Also trigger
when other skills need to construct filters — KFL is the query language for all
Kubeshark traffic analysis.
last-updated: 2026-05-08
---
# KFL2 — Kubeshark Filter Language
You are a KFL2 expert. KFL2 is built on Google's CEL (Common Expression Language)
and is the query language for all Kubeshark traffic analysis. It operates as a
**display filter** — it doesn't affect what's captured, only what you see.
Think of KFL the way you think of SQL for databases or Google search syntax for
the web. Kubeshark captures and indexes all cluster traffic; KFL is how you
search it.
For the complete variable and field reference, see `references/kfl2-reference.md`.
## Core Syntax
KFL expressions are boolean CEL expressions. An empty filter matches everything.
### Operators
| Category | Operators |
|----------|-----------|
| Comparison | `==`, `!=`, `<`, `<=`, `>`, `>=` |
| Logical | `&&`, `\|\|`, `!` |
| Arithmetic | `+`, `-`, `*`, `/`, `%` |
| Membership | `in` |
| Ternary | `condition ? true_val : false_val` |
### String Functions
```
str.contains(substring) // Substring search
str.startsWith(prefix) // Prefix match
str.endsWith(suffix) // Suffix match
str.matches(regex) // Regex match
size(str) // String length
```
### Collection Functions
```
size(collection) // List/map/string length
key in map // Key existence
map[key] // Value access
map_get(map, key, default) // Safe access with default
value in list // List membership
```
### Time Functions
```
timestamp("2026-03-14T22:00:00Z") // Parse ISO timestamp
duration("5m") // Parse duration
now() // Current time (snapshot at filter creation)
```
### Negation
```
!http // Everything that is NOT HTTP
http && status_code != 200 // HTTP responses that aren't 200
http && !path.contains("/health") // Exclude health checks
!(src.pod.namespace == "kube-system") // Exclude system namespace
```
## Protocol Detection
Boolean flags that indicate which protocol was detected. Use these as the first
filter term — they're fast and narrow the search space immediately.
| Flag | Protocol | Flag | Protocol |
|------|----------|------|----------|
| `http` | HTTP/1.1, HTTP/2 | `redis` | Redis |
| `dns` | DNS | `kafka` | Kafka |
| `tls` | eBPF TLS interception | `amqp` | AMQP |
| `tcp` | TCP | `ldap` | LDAP |
| `udp` | UDP | `ws` | WebSocket |
| `sctp` | SCTP | `gql` | GraphQL (v1+v2) |
| `icmp` | ICMP | `gqlv1` / `gqlv2` | GraphQL version-specific |
| `grpc` | gRPC (HTTP/2 sub-protocol) | `mongodb` | MongoDB |
| `mysql` | MySQL | `postgresql` | PostgreSQL |
| `radius` | RADIUS | | |
| `diameter` | Diameter | `conn` / `flow` | L4 connection/flow tracking |
| | | `tcp_conn` / `udp_conn` | Transport-specific connections |
## Kubernetes Context
The most common starting point. Filter by where traffic originates or terminates.
### Pod and Service Fields
```
src.pod.name == "orders-594487879c-7ddxf"
dst.pod.namespace == "production"
src.service.name == "api-gateway"
dst.service.namespace == "payments"
```
Pod fields fall back to service data when pod info is unavailable, so
`dst.pod.namespace` works even for service-level entries.
### Summary Name and Namespace
Convenience variables that pick the best available identity for a peer:
```
src.name == "api-gateway" // pod > service > dns > process
dst.name.contains("payment") // works across identity types
src.namespace == "production" // pod namespace, falls back to service
dst.namespace != "kube-system" // exclude system namespace
```
### Aggregate Collections
Match against any direction (src or dst):
```
"production" in namespaces // Any namespace match
"orders" in pods // Any pod name match
"api-gateway" in services // Any service name match
```
### Labels and Annotations
```
map_get(local_labels, "app", "") == "checkout" // Safe access with default
map_get(remote_labels, "version", "") == "canary"
"tier" in local_labels // Label existence check
```
Always use `map_get()` for labels and annotations — direct access like
`local_labels["app"]` errors if the key doesn't exist.
### Node and Process
```
node_name == "ip-10-0-25-170.ec2.internal"
local_process_name == "nginx"
remote_process_name.contains("postgres")
```
### DNS Resolution
```
src.dns == "api.example.com"
dst.dns.contains("redis")
```
## HTTP Filtering
HTTP is the most common protocol for API-level investigation.
### Fields
| Field | Type | Example |
|-------|------|---------|
| `method` | string | `"GET"`, `"POST"`, `"PUT"`, `"DELETE"` |
| `url` | string | Full path + query: `"/api/users?id=123"` |
| `path` | string | Path only: `"/api/users"` |
| `status_code` | int | `200`, `404`, `500` |
| `http_version` | string | `"HTTP/1.1"`, `"HTTP/2"` |
| `request.headers` | map | `request.headers["content-type"]` |
| `response.headers` | map | `response.headers["server"]` |
| `request.cookies` | map | `request.cookies["session"]` |
| `response.cookies` | map | `response.cookies["token"]` |
| `query_string` | map | `query_string["id"]` |
| `request_body_size` | int | Request body bytes |
| `response_body_size` | int | Response body bytes |
| `elapsed_time` | int | Duration in **microseconds** |
### Common Patterns
```
// Error investigation
http && status_code >= 500 // Server errors
http && status_code == 429 // Rate limiting
http && status_code >= 400 && status_code < 500 // Client errors
// Endpoint targeting
http && method == "POST" && path.contains("/orders")
http && url.matches(".*/api/v[0-9]+/users.*")
// Performance
http && elapsed_time > 5000000 // > 5 seconds
http && response_body_size > 1000000 // > 1MB responses
// Header inspection
http && "authorization" in request.headers
http && request.headers["content-type"] == "application/json"
// GraphQL (subset of HTTP)
gql && method == "POST" && status_code >= 400
// Only eBPF-intercepted TLS traffic (decrypted HTTPS)
tls && http && status_code >= 500
```
> **Note on `tls`**: The `tls` flag is an alias for `capture_source == "ebpf_tls"`.
> It indicates traffic captured via eBPF TLS interception, not TLS protocol dissection.
## DNS Filtering
DNS issues are often the hidden root cause of outages.
| Field | Type | Description |
|-------|------|-------------|
| `dns_questions` | []string | Question domain names |
| `dns_answers` | []string | Answer domain names |
| `dns_question_types` | []string | Record types: A, AAAA, CNAME, MX, TXT, SRV, PTR |
| `dns_request` | bool | Is request |
| `dns_response` | bool | Is response |
| `dns_request_length` | int | Request size |
| `dns_response_length` | int | Response size |
```
dns && "api.external-service.com" in dns_questions
dns && dns_response && status_code != 0 // Failed lookups
dns && "A" in dns_question_types // A record queries
dns && size(dns_questions) > 1 // Multi-question
```
## Database and Messaging Protocols
### Redis
```
redis && redis_type == "GET" // Command type
redis && redis_key.startsWith("session:") // Key pattern
redis && redis_command.contains("DEL") // Command search
redis && redis_total_size > 10000 // Large operations
```
### Kafka
```
kafka && kafka_api_key_name == "PRODUCE" // Produce operations
kafka && kafka_client_id == "payment-processor" // Client filtering
kafka && kafka_request_summary.contains("orders") // Topic filtering
kafka && kafka_size > 10000 // Large messages
```
### MongoDB
```
mongodb && mongodb_command == "find" // Find operations
mongodb && mongodb_collection == "users" // Collection filtering
mongodb && mongodb_database == "mydb" // Database filtering
mongodb && !mongodb_success // Failed operations
mongodb && mongodb_error_code != 0 // Error code filtering
mongodb && mongodb_total_size > 10000 // Large operations
```
### MySQL
```
mysql && mysql_command == "COM_QUERY" // SQL queries
mysql && mysql_query.contains("SELECT") // SELECT statements
mysql && mysql_database == "orders_db" // Database filtering
mysql && !mysql_success // Failed queries
mysql && mysql_error_code != 0 // Error code filtering
mysql && mysql_total_size > 10000 // Large queries
```
### PostgreSQL
```
postgresql && postgresql_command == "COM_QUERY" // Query commands
postgresql && postgresql_query.contains("SELECT") // SELECT statements
postgresql && postgresql_database == "orders_db" // Database filtering
postgresql && postgresql_user == "admin" // User filtering
postgresql && !postgresql_success // Failed queries
postgresql && postgresql_error_code != "" // Error code filtering (SQLSTATE string)
postgresql && postgresql_total_size > 10000 // Large queries
```
> **Note**: `postgresql_error_code` is a **string** (SQLSTATE code like `"23505"`),
> not an int. This differs from MySQL's `mysql_error_code` which is an int.
### gRPC
gRPC is a sub-protocol of HTTP/2. All HTTP variables are also available on gRPC entries.
```
grpc && grpc_method == "SayHello" // Method filtering
grpc && grpc_status != 0 // Non-OK status codes
grpc && grpc_status == 14 // UNAVAILABLE
grpc && grpc_method.contains("Create") // Method pattern
grpc && elapsed_time > 1000000 // Slow gRPC calls (>1s)
```
### AMQP, LDAP, RADIUS, Diameter
```
amqp && amqp_method == "basic.publish" // AMQP publish
ldap && ldap_type == "bind" // LDAP bind requests
radius && radius_code_name == "Access-Request" // RADIUS auth
diameter && diameter_method.contains("Credit") // Diameter credit control
```
For the full variable list for these protocols, see `references/kfl2-reference.md`.
## Transport Layer (L4)
### TCP/UDP Fields
```
tcp && tcp_error_type != "" // TCP errors
udp && udp_length > 1000 // Large UDP packets
```
### Connection Tracking
```
conn && conn_state == "open" // Active connections
conn && conn_local_bytes > 1000000 // High-volume
conn && "HTTP" in conn_l7_detected // L7 protocol detection
tcp_conn && conn_state == "closed" // Closed TCP connections
```
### Flow Tracking (with Rate Metrics)
```
flow && flow_local_pps > 1000 // High packet rate
flow && flow_local_bps > 1000000 // High bandwidth
flow && flow_state == "closed" && "TLS" in flow_l7_detected
tcp_flow && flow_local_bps > 5000000 // High-throughput TCP
```
## Network Layer
```
src.ip == "10.0.53.101"
dst.ip.startsWith("192.168.")
src.port == 8080
dst.port >= 8000 && dst.port <= 9000
```
## Time-Based Filtering
```
timestamp > timestamp("2026-03-14T22:00:00Z")
timestamp >= timestamp("2026-03-14T22:00:00Z") && timestamp <= timestamp("2026-03-14T23:00:00Z")
timestamp > now() - duration("5m") // Last 5 minutes
elapsed_time > 2000000 // Latency > 2 seconds
```
## Building Filters: Progressive Narrowing
The most effective investigation technique — start broad, add constraints:
```
// Step 1: Protocol + namespace
http && dst.pod.namespace == "production"
// Step 2: Add error condition
http && dst.pod.namespace == "production" && status_code >= 500
// Step 3: Narrow to service
http && dst.pod.namespace == "production" && status_code >= 500 && dst.service.name == "payment-service"
// Step 4: Narrow to endpoint
http && dst.pod.namespace == "production" && status_code >= 500 && dst.service.name == "payment-service" && path.contains("/charge")
// Step 5: Add timing
http && dst.pod.namespace == "production" && status_code >= 500 && dst.service.name == "payment-service" && path.contains("/charge") && elapsed_time > 2000000
```
## Performance Tips
1. **Protocol flags first**`http && ...` is faster than `... && http`
2. **`startsWith`/`endsWith` over `contains`** — prefix/suffix checks are faster
3. **Specific ports before string ops**`dst.port == 80` is cheaper than `url.contains(...)`
4. **Use `map_get` for labels** — avoids errors on missing keys
5. **Keep filters simple** — CEL short-circuits on `&&`, so put cheap checks first
## Type Safety
KFL2 is statically typed. Common gotchas:
- `status_code` is `int`, not string — use `status_code == 200`, not `"200"`
- `elapsed_time` is in **microseconds** — 5 seconds = `5000000`
- `timestamp` requires `timestamp()` function — not a raw string
- Map access on missing keys errors — use `key in map` or `map_get()` first
- List membership uses `value in list` — not `list.contains(value)`

View File

@@ -0,0 +1,491 @@
# KFL2 Complete Variable and Field Reference
> Last synced with [kfl2 repo](https://github.com/kubeshark/kfl2): 2026-05-08
This is the exhaustive reference for every variable available in KFL2 filters.
KFL2 is built on Google's CEL (Common Expression Language) and evaluates against
Kubeshark's protobuf-based `BaseEntry` structure.
## Most Commonly Used Variables
These are the variables you'll reach for in 90% of investigations:
| Variable | Type | What it's for |
|----------|------|---------------|
| `status_code` | int | HTTP response status (200, 404, 500) |
| `method` | string | HTTP method (GET, POST, PUT, DELETE) |
| `path` | string | URL path without query string |
| `dst.pod.namespace` | string | Where traffic is going (namespace) |
| `dst.service.name` | string | Where traffic is going (service) |
| `src.pod.name` | string | Where traffic comes from (pod) |
| `elapsed_time` | int | Request duration in microseconds |
| `dns_questions` | []string | DNS domains being queried |
| `namespaces` | []string | All namespaces involved (src + dst) |
## Network-Level Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `src.ip` | string | Source IP address | `"10.0.53.101"` |
| `dst.ip` | string | Destination IP address | `"192.168.1.1"` |
| `src.port` | int | Source port number | `43210` |
| `dst.port` | int | Destination port number | `8080` |
| `protocol` | string | Detected protocol type | `"HTTP"`, `"DNS"` |
## Identity and Metadata Variables
| Variable | Type | Description |
|----------|------|-------------|
| `id` | int | BaseEntry unique identifier (assigned by sniffer) |
| `node_id` | string | Node identifier (assigned by hub) |
| `index` | int | Entry index for stream uniqueness |
| `stream` | string | Stream identifier (hex string) |
| `timestamp` | timestamp | Event time (UTC), use with `timestamp()` function |
| `elapsed_time` | int | Response-request latency in microseconds |
| `worker` | string | Worker identifier |
## Cross-Reference Variables
| Variable | Type | Description |
|----------|------|-------------|
| `conn_id` | int | L7 to L4 connection cross-reference ID |
| `flow_id` | int | L7 to L4 flow cross-reference ID |
| `has_pcap` | bool | Whether PCAP data is available for this entry |
## Capture Source Variables
| Variable | Type | Description | Values |
|----------|------|-------------|--------|
| `capture_source` | string | Canonical capture source | `"unspecified"`, `"af_packet"`, `"ebpf"`, `"ebpf_tls"` |
| `capture_backend` | string | Backend family | `"af_packet"`, `"ebpf"` |
| `capture_source_code` | int | Numeric enum | 0=unspecified, 1=af_packet, 2=ebpf, 3=ebpf_tls |
| `capture` | map | Nested map access | `capture["source"]`, `capture["backend"]` |
## Protocol Detection Flags
Boolean variables indicating detected protocol. Use as first filter term for performance.
| Variable | Protocol | Variable | Protocol |
|----------|----------|----------|----------|
| `http` | HTTP/1.1, HTTP/2 | `redis` | Redis |
| `dns` | DNS | `kafka` | Kafka |
| `tls` | eBPF TLS interception | `amqp` | AMQP messaging |
| `tcp` | TCP transport | `ldap` | LDAP directory |
| `udp` | UDP transport | `ws` | WebSocket |
| `sctp` | SCTP streaming | `gql` | GraphQL (v1 or v2) |
| `icmp` | ICMP | `gqlv1` | GraphQL v1 only |
| `grpc` | gRPC (HTTP/2 sub-protocol) | `gqlv2` | GraphQL v2 only |
| `mongodb` | MongoDB | `mysql` | MySQL |
| `postgresql` | PostgreSQL | `diameter` | Diameter |
| `radius` | RADIUS auth | | |
| | | `conn` | L4 connection tracking |
| `flow` | L4 flow tracking | `tcp_conn` | TCP connection tracking |
| `tcp_flow` | TCP flow tracking | `udp_conn` | UDP connection tracking |
| `udp_flow` | UDP flow tracking | | |
## HTTP Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `method` | string | HTTP method | `"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, `"PATCH"` |
| `url` | string | Full URL path and query string | `"/api/users?id=123"` |
| `path` | string | URL path component (no query) | `"/api/users"` |
| `status_code` | int | HTTP response status code | `200`, `404`, `500` |
| `http_version` | string | HTTP protocol version | `"HTTP/1.1"`, `"HTTP/2"` |
| `query_string` | map[string]string | Parsed URL query parameters | `query_string["id"]``"123"` |
| `request.headers` | map[string]string | Request HTTP headers | `request.headers["content-type"]` |
| `response.headers` | map[string]string | Response HTTP headers | `response.headers["server"]` |
| `request.cookies` | map[string]string | Request cookies | `request.cookies["session"]` |
| `response.cookies` | map[string]string | Response cookies | `response.cookies["token"]` |
| `request_headers_size` | int | Request headers size in bytes | |
| `request_body_size` | int | Request body size in bytes | |
| `response_headers_size` | int | Response headers size in bytes | |
| `response_body_size` | int | Response body size in bytes | |
GraphQL requests have `gql` (or `gqlv1`/`gqlv2`) set to true and all HTTP
variables available.
**Example**: `http && method == "POST" && status_code >= 500 && path.contains("/api")`
## DNS Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `dns_questions` | []string | Question domain names (request + response) | `["example.com"]` |
| `dns_answers` | []string | Answer domain names | `["1.2.3.4"]` |
| `dns_question_types` | []string | Record types in questions | `["A"]`, `["AAAA"]`, `["CNAME"]` |
| `dns_request` | bool | Is DNS request message | |
| `dns_response` | bool | Is DNS response message | |
| `dns_request_length` | int | DNS request size in bytes (0 if absent) | |
| `dns_response_length` | int | DNS response size in bytes (0 if absent) | |
| `dns_total_size` | int | Sum of request + response sizes | |
Supported question types: A, AAAA, NS, CNAME, SOA, MX, TXT, SRV, PTR, ANY.
**Example**: `dns && dns_response && status_code != 0` (failed DNS lookups)
## TLS Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `tls` | bool | eBPF TLS interception (alias for `capture_source == "ebpf_tls"`) | |
| `tls_summary` | string | TLS handshake summary | `"ClientHello"`, `"ServerHello"` |
| `tls_info` | string | TLS connection details | `"TLS 1.3, AES-256-GCM"` |
| `tls_request_size` | int | TLS request size in bytes | |
| `tls_response_size` | int | TLS response size in bytes | |
| `tls_total_size` | int | Sum of request + response (computed if not provided) | |
## TCP Variables
| Variable | Type | Description |
|----------|------|-------------|
| `tcp` | bool | TCP payload detected |
| `tcp_method` | string | TCP method information |
| `tcp_payload` | bytes | Raw TCP payload data |
| `tcp_error_type` | string | TCP error type (empty if none) |
| `tcp_error_message` | string | TCP error message (empty if none) |
## UDP Variables
| Variable | Type | Description |
|----------|------|-------------|
| `udp` | bool | UDP payload detected |
| `udp_length` | int | UDP packet length |
| `udp_checksum` | int | UDP checksum value |
| `udp_payload` | bytes | Raw UDP payload data |
## SCTP Variables
| Variable | Type | Description |
|----------|------|-------------|
| `sctp` | bool | SCTP payload detected |
| `sctp_checksum` | int | SCTP checksum value |
| `sctp_chunk_type` | string | SCTP chunk type |
| `sctp_length` | int | SCTP chunk length |
## ICMP Variables
| Variable | Type | Description |
|----------|------|-------------|
| `icmp` | bool | ICMP payload detected |
| `icmp_type` | string | ICMP type code |
| `icmp_version` | int | ICMP version (4 or 6) |
| `icmp_length` | int | ICMP message length |
## WebSocket Variables
| Variable | Type | Description | Values |
|----------|------|-------------|--------|
| `ws` | bool | WebSocket payload detected | |
| `ws_opcode` | string | WebSocket operation code | `"text"`, `"binary"`, `"close"`, `"ping"`, `"pong"` |
| `ws_request` | bool | Is WebSocket request | |
| `ws_response` | bool | Is WebSocket response | |
| `ws_request_payload_data` | string | Request payload (safely truncated) | |
| `ws_request_payload_length` | int | Request payload length in bytes | |
| `ws_response_payload_length` | int | Response payload length in bytes | |
## Redis Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `redis` | bool | Redis payload detected | |
| `redis_type` | string | Redis command verb | `"GET"`, `"SET"`, `"DEL"`, `"HGET"` |
| `redis_command` | string | Full Redis command line | `"GET session:1234"` |
| `redis_key` | string | Key (truncated to 64 bytes) | `"session:1234"` |
| `redis_request_size` | int | Request size (0 if absent) | |
| `redis_response_size` | int | Response size (0 if absent) | |
| `redis_total_size` | int | Sum of request + response | |
**Example**: `redis && redis_type == "GET" && redis_key.startsWith("session:")`
## Kafka Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `kafka` | bool | Kafka payload detected | |
| `kafka_api_key` | int | Kafka API key number | 0=FETCH, 1=PRODUCE |
| `kafka_api_key_name` | string | Human-readable API operation | `"PRODUCE"`, `"FETCH"` |
| `kafka_client_id` | string | Kafka client identifier | `"payment-processor"` |
| `kafka_size` | int | Message size (request preferred, else response) | |
| `kafka_request` | bool | Is Kafka request | |
| `kafka_response` | bool | Is Kafka response | |
| `kafka_request_summary` | string | Request summary/topic | `"orders-topic"` |
| `kafka_request_size` | int | Request size (0 if absent) | |
| `kafka_response_size` | int | Response size (0 if absent) | |
**Example**: `kafka && kafka_api_key_name == "PRODUCE" && kafka_request_summary.contains("orders")`
## AMQP Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `amqp` | bool | AMQP payload detected | |
| `amqp_method` | string | AMQP method name | `"basic.publish"`, `"channel.open"` |
| `amqp_summary` | string | Operation summary | |
| `amqp_request` | bool | Is AMQP request | |
| `amqp_response` | bool | Is AMQP response | |
| `amqp_request_length` | int | Request length (0 if absent) | |
| `amqp_response_length` | int | Response length (0 if absent) | |
| `amqp_total_size` | int | Sum of request + response | |
## LDAP Variables
| Variable | Type | Description |
|----------|------|-------------|
| `ldap` | bool | LDAP payload detected |
| `ldap_type` | string | LDAP operation type (request preferred) |
| `ldap_summary` | string | Operation summary |
| `ldap_request` | bool | Is LDAP request |
| `ldap_response` | bool | Is LDAP response |
| `ldap_request_length` | int | Request length (0 if absent) |
| `ldap_response_length` | int | Response length (0 if absent) |
| `ldap_total_size` | int | Sum of request + response |
## RADIUS Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `radius` | bool | RADIUS payload detected | |
| `radius_code` | int | RADIUS code (request preferred) | |
| `radius_code_name` | string | Code name | `"Access-Request"` |
| `radius_request` | bool | Is RADIUS request | |
| `radius_response` | bool | Is RADIUS response | |
| `radius_request_authenticator` | string | Request authenticator (hex) | |
| `radius_request_length` | int | Request size (0 if absent) | |
| `radius_response_length` | int | Response size (0 if absent) | |
| `radius_total_size` | int | Sum of request + response | |
## Diameter Variables
| Variable | Type | Description |
|----------|------|-------------|
| `diameter` | bool | Diameter payload detected |
| `diameter_method` | string | Method name (request preferred) |
| `diameter_summary` | string | Operation summary |
| `diameter_request` | bool | Is Diameter request |
| `diameter_response` | bool | Is Diameter response |
| `diameter_request_length` | int | Request size (0 if absent) |
| `diameter_response_length` | int | Response size (0 if absent) |
| `diameter_total_size` | int | Sum of request + response |
## MongoDB Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `mongodb` | bool | MongoDB payload detected | |
| `mongodb_command` | string | Operation type | `"find"`, `"insert"`, `"update"`, `"delete"` |
| `mongodb_database` | string | Database name | `"mydb"` |
| `mongodb_collection` | string | Collection name | `"users"` |
| `mongodb_opcode` | string | Operation opcode name | |
| `mongodb_request_size` | int | Request size in bytes | |
| `mongodb_response_size` | int | Response size in bytes | |
| `mongodb_total_size` | int | Combined request + response size | |
| `mongodb_success` | bool | Operation success status | |
| `mongodb_error_code` | int | Error code | |
| `mongodb_error_message` | string | Error description | |
| `mongodb_error_code_name` | string | Named error code | |
**Example**: `mongodb && mongodb_command == "find" && mongodb_collection == "users"`
## MySQL Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `mysql` | bool | MySQL payload detected | |
| `mysql_command` | string | SQL command name | `"COM_QUERY"`, `"COM_STMT_PREPARE"` |
| `mysql_query` | string | Full SQL query text | `"SELECT * FROM users"` |
| `mysql_database` | string | Active database name | `"orders_db"` |
| `mysql_statement_id` | int | Prepared statement identifier | |
| `mysql_request_size` | int | Request payload size in bytes | |
| `mysql_response_size` | int | Response payload size in bytes | |
| `mysql_total_size` | int | Combined request + response size | |
| `mysql_success` | bool | Response OK status | |
| `mysql_error_code` | int | MySQL error code | |
| `mysql_error_message` | string | Error description | |
**Example**: `mysql && mysql_query.contains("SELECT") && !mysql_success`
## PostgreSQL Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `postgresql` | bool | PostgreSQL payload detected | |
| `postgresql_command` | string | Command tag | `"SELECT"`, `"INSERT"`, `"UPDATE"` |
| `postgresql_query` | string | Full SQL query text | `"SELECT * FROM users WHERE id = 1"` |
| `postgresql_database` | string | Active database name | `"orders_db"` |
| `postgresql_user` | string | Authenticated user name | `"app_service"` |
| `postgresql_request_size` | int | Request payload size in bytes | |
| `postgresql_response_size` | int | Response payload size in bytes | |
| `postgresql_total_size` | int | Combined request + response size | |
| `postgresql_success` | bool | Response OK status | |
| `postgresql_error_code` | **string** | SQLSTATE error code (NOT int) | `"23505"` (unique violation), `"42P01"` (undefined table) |
| `postgresql_error_message` | string | Error description | |
**Important**: Unlike MySQL's `mysql_error_code` (int), `postgresql_error_code` is a
**string** because PostgreSQL uses 5-character SQLSTATE codes.
**Example**: `postgresql && postgresql_query.contains("SELECT") && !postgresql_success`
## gRPC Variables
gRPC is a sub-protocol of HTTP/2. When `grpc` is true, all HTTP variables are also available.
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `grpc` | bool | gRPC payload detected | |
| `grpc_method` | string | Trailing method name from gRPC :path | `"SayHello"` (from `/helloworld.Greeter/SayHello`) |
| `grpc_status` | int | gRPC status code from Grpc-Status trailer | `0`=OK, `5`=NOT_FOUND, `14`=UNAVAILABLE; `-1` on non-gRPC |
**Example**: `grpc && grpc_status != 0 && grpc_method.contains("Create")`
## L4 Connection Tracking Variables
| Variable | Type | Description | Example |
|----------|------|-------------|---------|
| `conn` | bool | Connection tracking entry | |
| `conn_state` | string | Connection state | `"open"`, `"in_progress"`, `"closed"` |
| `conn_local_pkts` | int | Packets from local peer | |
| `conn_local_bytes` | int | Bytes from local peer | |
| `conn_remote_pkts` | int | Packets from remote peer | |
| `conn_remote_bytes` | int | Bytes from remote peer | |
| `conn_l7_detected` | []string | L7 protocols detected on connection | `["HTTP", "TLS"]` |
| `conn_group_id` | int | Connection group identifier | |
**Example**: `conn && conn_state == "open" && conn_local_bytes > 1000000` (high-volume open connections)
## L4 Flow Tracking Variables
Flows extend connections with rate metrics (packets/bytes per second).
| Variable | Type | Description |
|----------|------|-------------|
| `flow` | bool | Flow tracking entry |
| `flow_state` | string | Flow state (`"open"`, `"in_progress"`, `"closed"`) |
| `flow_local_pkts` | int | Packets from local peer |
| `flow_local_bytes` | int | Bytes from local peer |
| `flow_remote_pkts` | int | Packets from remote peer |
| `flow_remote_bytes` | int | Bytes from remote peer |
| `flow_local_pps` | int | Local packets per second |
| `flow_local_bps` | int | Local bytes per second |
| `flow_remote_pps` | int | Remote packets per second |
| `flow_remote_bps` | int | Remote bytes per second |
| `flow_l7_detected` | []string | L7 protocols detected on flow |
| `flow_group_id` | int | Flow group identifier |
**Example**: `tcp_flow && flow_local_bps > 5000000` (high-bandwidth TCP flows)
## Kubernetes Variables
### Pod and Service (Directional)
| Variable | Type | Description |
|----------|------|-------------|
| `src.pod.name` | string | Source pod name |
| `src.pod.namespace` | string | Source pod namespace |
| `dst.pod.name` | string | Destination pod name |
| `dst.pod.namespace` | string | Destination pod namespace |
| `src.service.name` | string | Source service name |
| `src.service.namespace` | string | Source service namespace |
| `dst.service.name` | string | Destination service name |
| `dst.service.namespace` | string | Destination service namespace |
**Fallback behavior**: Pod namespace/name fields automatically fall back to
service data when pod info is unavailable. This means `dst.pod.namespace` works
even when only service-level resolution exists.
**Example**: `src.service.name == "api-gateway" && dst.pod.namespace == "production"`
### Summary Name and Namespace
| Variable | Type | Description |
|----------|------|-------------|
| `src.name` | string | Worker-enriched summary name of source (pod > service > dns > process) |
| `dst.name` | string | Worker-enriched summary name of destination |
| `src.namespace` | string | Source namespace with service fallback |
| `dst.namespace` | string | Destination namespace with service fallback |
### Aggregate Collections (Non-Directional)
| Variable | Type | Description |
|----------|------|-------------|
| `namespaces` | []string | All namespaces (src + dst, pod + service) |
| `pods` | []string | All pod names (src + dst) |
| `services` | []string | All service names (src + dst) |
### Labels and Annotations
| Variable | Type | Description |
|----------|------|-------------|
| `local_labels` | map[string]string | Kubernetes labels of local peer |
| `local_annotations` | map[string]string | Kubernetes annotations of local peer |
| `remote_labels` | map[string]string | Kubernetes labels of remote peer |
| `remote_annotations` | map[string]string | Kubernetes annotations of remote peer |
Use `map_get(local_labels, "key", "default")` for safe access that won't error
on missing keys.
**Example**: `map_get(local_labels, "app", "") == "checkout" && "production" in namespaces`
### Node Information
| Variable | Type | Description |
|----------|------|-------------|
| `node` | map | Nested: `node["name"]`, `node["ip"]` |
| `node_name` | string | Node name (flat alias) |
| `node_ip` | string | Node IP (flat alias) |
| `local_node_name` | string | Node name of local peer |
| `remote_node_name` | string | Node name of remote peer |
### Process Information
| Variable | Type | Description |
|----------|------|-------------|
| `local_process_name` | string | Process name on local peer |
| `remote_process_name` | string | Process name on remote peer |
### DNS Resolution
| Variable | Type | Description |
|----------|------|-------------|
| `src.dns` | string | DNS resolution of source IP |
| `dst.dns` | string | DNS resolution of destination IP |
| `dns_resolutions` | []string | All DNS resolutions (deduplicated) |
### Resolution Status
| Variable | Type | Values |
|----------|------|--------|
| `local_resolution_status` | string | `""` (resolved), `"no_node_mapping"`, `"rpc_error"`, `"rpc_empty"`, `"cache_miss"`, `"queue_full"` |
| `remote_resolution_status` | string | Same as above |
## Default Values
When a variable is not present in an entry, KFL2 uses these defaults:
| Type | Default |
|------|---------|
| string | `""` |
| int | `0` |
| bool | `false` |
| list | `[]` |
| map | `{}` |
| bytes | `[]` |
## Protocol Variable Precedence
For protocols with request/response pairs (Kafka, RADIUS, Diameter), merged
fields prefer the **request** side. If no request exists, the response value
is used. Size totals are always computed as `request_size + response_size`.
## CEL Language Features
KFL2 supports the full CEL specification:
- **Short-circuit evaluation**: `&&` stops on first false, `||` stops on first true
- **Ternary**: `condition ? value_if_true : value_if_false`
- **Regex**: `str.matches("pattern")` uses RE2 syntax
- **Type coercion**: Timestamps require `timestamp()`, durations require `duration()`
- **Null safety**: Use `in` operator or `map_get()` before accessing map keys
For the full CEL specification, see the
[CEL Language Definition](https://github.com/google/cel-spec/blob/master/doc/langdef.md).

484
skills/network-rca/SKILL.md Normal file
View File

@@ -0,0 +1,484 @@
---
name: network-rca
description: >
Kubernetes network root cause analysis skill powered by Kubeshark MCP. Use this skill
whenever the user wants to investigate past incidents, perform retrospective traffic
analysis, take or manage traffic snapshots, extract PCAPs, dissect L7 API calls from
historical captures, compare traffic patterns over time, detect drift or anomalies
between snapshots, or do any kind of forensic network analysis in Kubernetes.
Also trigger when the user mentions snapshots, raw capture, PCAP extraction,
traffic replay, postmortem analysis, "what happened yesterday/last week",
root cause analysis, RCA, cloud snapshot storage, snapshot dissection, or KFL filters
for historical traffic. Even if the user just says "figure out what went wrong"
or "compare today's traffic to yesterday" in a Kubernetes context, use this skill.
---
# Network Root Cause Analysis with Kubeshark MCP
You are a Kubernetes network forensics specialist. Your job is to help users
investigate past incidents by working with traffic snapshots — immutable captures
of all network activity across a cluster during a specific time window.
Kubeshark is a search engine for network traffic. Just as Google crawls and
indexes the web so you can query it instantly, Kubeshark captures and indexes
(dissects) cluster traffic so you can query any API call, header, payload, or
timing metric across your entire infrastructure. Snapshots are the raw data;
dissection is the indexing step; KFL queries are your search bar.
Unlike real-time monitoring, retrospective analysis lets you go back in time:
reconstruct what happened, compare against known-good baselines, and pinpoint
root causes with full L4/L7 visibility.
## Timezone Handling
All timestamps presented to the user **must use the local timezone** of the environment
where the agent is running. Users think in local time ("this happened around 3pm"), and
UTC-only output adds friction during incident response when speed matters.
### Rules
1. **Detect the local timezone** at the start of every investigation. Use the system
clock or environment (e.g., `date +%Z` or equivalent) to determine the timezone.
2. **Present local time as the primary reference** in all output — summaries, event
correlations, time-range references, and tables.
3. **Show UTC in parentheses** for clarity, e.g., `15:03:22 IST (12:03:22 UTC)`.
4. **Convert tool responses** — Kubeshark MCP tools return timestamps in UTC. Always
convert these to local time before presenting to the user.
5. **Use local time in natural language** — when describing events, say "the spike at
3:23 PM" not "the spike at 12:23 UTC".
### Snapshot Creation
When creating snapshots, Kubeshark MCP tools accept UTC timestamps. Convert the user's
local time references to UTC before passing them to tools like `create_snapshot` or
`export_snapshot_pcap`. Confirm the converted window with the user if there's any
ambiguity.
## Prerequisites
Before starting any analysis, verify the environment is ready.
### Kubeshark MCP Health Check
Confirm the Kubeshark MCP is accessible and tools are available. Look for tools
like `list_api_calls`, `list_l4_flows`, `create_snapshot`, etc.
**Tool**: `check_kubeshark_status`
If tools like `list_api_calls` or `list_l4_flows` are missing from the response,
something is wrong with the MCP connection. Guide the user through setup
(see Setup Reference at the bottom).
### Raw Capture Must Be Enabled
Retrospective analysis depends on raw capture — Kubeshark's kernel-level (eBPF)
packet recording that stores traffic at the node level. Without it, snapshots
have nothing to work with.
Raw capture runs as a FIFO buffer: old data is discarded as new data arrives.
The buffer size determines how far back you can go. Larger buffer = wider
snapshot window.
```yaml
tap:
capture:
raw:
enabled: true
storageSize: 10Gi # Per-node FIFO buffer
```
If raw capture isn't enabled, inform the user that retrospective analysis
requires it and share the configuration above.
### Snapshot Storage
Snapshots are assembled on the Hub's storage, which is ephemeral by default.
For serious forensic work, persistent storage is recommended:
```yaml
tap:
snapshots:
local:
storageClass: gp2
storageSize: 1000Gi
```
## Core Workflow
Every investigation starts with a snapshot. After that, you choose one of two
investigation routes depending on your goal:
1. **Determine time window** — When did the issue occur? Use `get_data_boundaries`
to see what raw capture data (L4) is available.
2. **Check the L7 (dissected) window** — Before any KFL query on *live* data,
call `get_l7_data_boundaries`. It returns the per-node + cluster-wide range
of dissected API call data plus a `dissection_enabled` flag. Treat L4
(`get_data_boundaries`) as the snapshot/PCAP window and L7
(`get_l7_data_boundaries`) as the KFL-query window — they can differ
significantly because L7 only starts producing entries once dissection is
enabled (existing raw capture is **not** retroactively dissected).
3. **Create or locate a snapshot** — Either take a new snapshot covering the
incident window, or find an existing one with `list_snapshots`.
4. **Choose your investigation route** — PCAP or Dissection (see below).
### Choosing the Right Route
| | PCAP Route | Dissection Route |
|---|---|---|
| **Speed** | Immediate — no indexing needed | Takes time to index |
| **Filtering** | Nodes, time window, BPF filters | Kubernetes & API-level (pods, labels, paths, status codes) |
| **Output** | Cluster-wide PCAP files | Structured query results |
| **Investigation by** | Human (Wireshark) | AI agent or human (queryable database) |
| **Best for** | Compliance, sharing with network teams, Wireshark deep-dives | Root cause analysis, API-level debugging, automated investigation |
Both routes are valid and complementary. Use PCAP when you need raw packets
for human analysis or compliance. Use Dissection when you want an AI agent
to search and analyze traffic programmatically.
**Default to Dissection.** Unless the user explicitly asks for a PCAP file or
Wireshark export, assume Dissection is needed. Any question about workloads,
APIs, services, pods, error rates, latency, or traffic patterns requires
dissected data.
## Snapshot Operations
Both routes start here. A snapshot is an immutable freeze of all cluster traffic
in a time window.
### Check Data Boundaries
**Tool**: `get_data_boundaries`
Check what raw capture data exists across the cluster. You can only create
snapshots within these boundaries — data outside the window has been rotated
out of the FIFO buffer.
**Example response** (raw tool output is in UTC — convert to local time before presenting):
```
Cluster-wide:
Oldest: 2026-03-14 18:12:34 IST (16:12:34 UTC)
Newest: 2026-03-14 20:05:20 IST (18:05:20 UTC)
Per node:
┌─────────────────────────────┬───────────────────────────────┬───────────────────────────────┐
│ Node │ Oldest │ Newest │
├─────────────────────────────┼───────────────────────────────┼───────────────────────────────┤
│ ip-10-0-25-170.ec2.internal │ 18:12:34 IST (16:12:34 UTC) │ 20:03:39 IST (18:03:39 UTC) │
│ ip-10-0-32-115.ec2.internal │ 18:13:45 IST (16:13:45 UTC) │ 20:05:20 IST (18:05:20 UTC) │
└─────────────────────────────┴───────────────────────────────┴───────────────────────────────┘
```
If the incident falls outside the available window, the data has been rotated
out. Suggest increasing `storageSize` for future coverage.
### Check L7 (Dissected) Data Boundaries
**Tool**: `get_l7_data_boundaries`
Check what *dissected* L7 entries exist across the cluster. This is the
pre-flight check before any KFL query against live data. The response
contains:
- `dissection_enabled`: if `false`, KFL queries on live data will return
empty regardless of L4 boundaries. Enabling dissection only captures
*forward* — raw capture is **not** retroactively dissected.
- `cluster.oldest_ts` / `cluster.newest_ts`: cluster-wide window where KFL
on live data has any chance of returning results.
- `nodes[].oldest_ts` / `nodes[].newest_ts`: per-node windows for narrowing
queries.
**Key distinction:**
| | L4 (`get_data_boundaries`) | L7 (`get_l7_data_boundaries`) |
|---|---|---|
| Data | Raw PCAP capture | Dissected API call entries |
| Useful for | Snapshots, PCAP extraction | KFL queries |
| Backfill | Comes from FIFO ring buffer | Only forward from dissection-enable |
If the user is asking an API-level question and `dissection_enabled` is
`false`, enable it first — but tell the user they will only see entries
captured *after* enabling, never the historical window.
### Create a Snapshot
**Tool**: `create_snapshot`
Specify nodes (or cluster-wide) and a time window within the data boundaries.
Snapshots include raw capture files, Kubernetes pod events, and eBPF cgroup events.
Snapshots take time to build. Check status with `get_snapshot` — wait until
`completed` before proceeding with either route.
### List Existing Snapshots
**Tool**: `list_snapshots`
Shows all snapshots on the local Hub, with name, size, status, and node count.
### Cloud Storage
Snapshots on the Hub are ephemeral. Cloud storage (S3, GCS, Azure Blob)
provides long-term retention. Snapshots can be downloaded to any cluster
with Kubeshark — not necessarily the original one.
**Check cloud status**: `get_cloud_storage_status`
**Upload to cloud**: `upload_snapshot_to_cloud`
**Download from cloud**: `download_snapshot_from_cloud`
---
## Route 1: PCAP
The PCAP route does **not** require dissection. It works directly with the raw
snapshot data to produce filtered, cluster-wide PCAP files. Use this route when:
- You need raw packets for Wireshark analysis
- You're sharing captures with network teams
- You need evidence for compliance or audit
- A human will perform the investigation (not an AI agent)
### Filtering a PCAP
**Tool**: `export_snapshot_pcap`
Filter the snapshot down to what matters using:
- **Nodes** — specific cluster nodes only
- **Time** — sub-window within the snapshot
- **BPF filter** — standard Berkeley Packet Filter syntax (e.g., `host 10.0.53.101`,
`port 8080`, `net 10.0.0.0/16`)
These filters are combinable — select specific nodes, narrow the time range,
and apply a BPF expression all at once.
### Workload-to-BPF Workflow
When you know the workload names but not their IPs, resolve them from the
snapshot's metadata. Snapshots preserve pod-to-IP mappings from capture time,
so resolution is accurate even if pods have been rescheduled since.
**Tool**: `list_workloads`
Use `list_workloads` with `name` + `namespace` for a singular lookup (works
live and against snapshots), or with `snapshot_id` + filters for a broader
scan.
**Example workflow — singular lookup** — extract PCAP for specific workloads:
1. Resolve IPs: `list_workloads` with `name: "orders-594487879c-7ddxf"`, `namespace: "prod"` → IPs: `["10.0.53.101"]`
2. Resolve IPs: `list_workloads` with `name: "payment-service-6b8f9d-x2k4p"`, `namespace: "prod"` → IPs: `["10.0.53.205"]`
3. Build BPF: `host 10.0.53.101 or host 10.0.53.205`
4. Export: `export_snapshot_pcap` with that BPF filter
**Example workflow — filtered scan** — extract PCAP for all workloads
matching a pattern in a snapshot:
1. List workloads: `list_workloads` with `snapshot_id`, `namespaces: ["prod"]`,
`name_regex: "payment.*"` → returns all matching workloads with their IPs
2. Collect all IPs from the response
3. Build BPF: `host 10.0.53.205 or host 10.0.53.210 or ...`
4. Export: `export_snapshot_pcap` with that BPF filter
This gives you a cluster-wide PCAP filtered to exactly the workloads involved
in the incident — ready for Wireshark or long-term storage.
### IP-to-Workload Resolution
When you have an IP address (e.g., from a PCAP or L4 flow) and need to
identify the workload behind it:
**Tool**: `list_ips`
Use `list_ips` with `ip` for a singular lookup (works live and against
snapshots), or with `snapshot_id` + filters for a broader scan.
**Example — singular lookup**: `list_ips` with `ip: "10.0.53.101"`,
`snapshot_id: "snap-abc"` → returns pod/service identity for that IP.
**Example — filtered scan**: `list_ips` with `snapshot_id: "snap-abc"`,
`namespaces: ["prod"]`, `labels: {"app": "payment"}` → returns all IPs
associated with workloads matching those filters.
---
## Route 2: Dissection
The Dissection route indexes raw packets into structured L7 API calls, building
a queryable database from the snapshot. Use this route when:
- An AI agent is performing the investigation
- You need to search by Kubernetes context (pods, namespaces, labels, services)
- You need to search by API elements (paths, status codes, headers, payloads)
- You want structured responses you can analyze programmatically
- You need to drill into the payload of a specific API call
**KFL requirement**: The Dissection route uses KFL filters for all queries
(`list_api_calls`, `get_api_stats`, etc.). Before constructing any KFL filter,
load the KFL skill (`skills/kfl/`). KFL is statically typed — incorrect field
names or syntax will fail silently or error. If the KFL skill is not available,
suggest the user install it:
```bash
ln -s /path/to/kubeshark/skills/kfl ~/.claude/skills/kfl
```
**If the KFL skill cannot be loaded**, only use the exact filter examples shown
in this skill. Do not improvise or guess at field names, operators, or syntax.
KFL field names differ from what you might expect (e.g., `status_code` not
`response.status`, `src.pod.namespace` not `src.namespace`). Using incorrect
fields produces wrong results without warning.
### Dissection Is Required — Do Not Skip This
**Any question about workloads, Kubernetes resources, services, pods, namespaces,
or API calls requires dissection.** Only the PCAP route works without it. If the
user asks anything about traffic content, API behavior, error rates, latency,
or service-to-service communication, you **must** ensure dissection is active
before attempting to answer.
**Do not wait for dissection to complete on its own — it will not start by itself.**
Follow this sequence every time before using `list_api_calls`, `get_api_call`,
or `get_api_stats`:
1. **Check status**: Call `get_snapshot_dissection_status` (or `list_snapshot_dissections`)
to see if a dissection already exists for this snapshot.
2. **If dissection exists and is completed** — proceed with your query. No further
action needed.
3. **If dissection is in progress** — wait for it to complete, then proceed.
4. **If no dissection exists** — you **must** call `start_snapshot_dissection` to
trigger it. Then monitor progress with `get_snapshot_dissection_status` until
it completes.
Never assume dissection is running. Never wait for a dissection that was not started.
The agent is responsible for triggering dissection when it is missing.
**Tool**: `start_snapshot_dissection`
Dissection takes time proportional to snapshot size — it parses every packet,
reassembles streams, and builds the index. After completion, these tools
become available:
- `list_api_calls` — Search API transactions with KFL filters
- `get_api_call` — Drill into a specific call (headers, body, timing, payload)
- `get_api_stats` — Aggregated statistics (throughput, error rates, latency)
### Every Question Is a Query
**Every user prompt that involves APIs, workloads, services, pods, namespaces,
or Kubernetes semantics should translate into a `list_api_calls` call with an
appropriate KFL filter.** Do not answer from memory or prior results — always
run a fresh query that matches what the user is asking.
Examples of user prompts and the queries they should trigger:
| User says | Action |
|---|---|
| "Show me all 500 errors" | `list_api_calls` with KFL: `http && status_code == 500` |
| "What's hitting the payment service?" | `list_api_calls` with KFL: `dst.service.name == "payment-service"` |
| "Any DNS failures?" | `list_api_calls` with KFL: `dns && status_code != 0` |
| "Show traffic from namespace prod to staging" | `list_api_calls` with KFL: `src.pod.namespace == "prod" && dst.pod.namespace == "staging"` |
| "What are the slowest API calls?" | `list_api_calls` with KFL: `http && elapsed_time > 5000000` |
The user's natural language maps to KFL. Your job is to translate intent into
the right filter and run the query — don't summarize old results or speculate
without fresh data.
### Investigation Strategy
Start broad, then narrow:
1. `get_api_stats` — Get the overall picture: error rates, latency percentiles,
throughput. Look for spikes or anomalies.
2. `list_api_calls` filtered by error codes (4xx, 5xx) or high latency — find
the problematic transactions.
3. `get_api_call` on specific calls — inspect headers, bodies, timing, and
full payload to understand what went wrong.
4. Use KFL filters to slice by namespace, service, protocol, or any combination.
**Example `list_api_calls` response** (filtered to `http && status_code >= 500`,
timestamps converted from UTC to local):
```
┌──────────────────────────────────────────┬────────┬──────────────────────────┬────────┬───────────┐
│ Timestamp │ Method │ URL │ Status │ Elapsed │
├──────────────────────────────────────────┼────────┼──────────────────────────┼────────┼───────────┤
│ 2026-03-14 19:23:45 IST (17:23:45 UTC) │ POST │ /api/v1/orders/charge │ 503 │ 12,340 ms │
│ 2026-03-14 19:23:46 IST (17:23:46 UTC) │ POST │ /api/v1/orders/charge │ 503 │ 11,890 ms │
│ 2026-03-14 19:23:48 IST (17:23:48 UTC) │ GET │ /api/v1/inventory/check │ 500 │ 8,210 ms │
│ 2026-03-14 19:24:01 IST (17:24:01 UTC) │ POST │ /api/v1/payments/process │ 502 │ 30,000 ms │
└──────────────────────────────────────────┴────────┴──────────────────────────┴────────┴───────────┘
Src: api-gateway (prod) → Dst: payment-service (prod)
```
Use the pattern of repeated failures and high latency to identify the failing
service chain, then drill into individual calls with `get_api_call`.
### KFL Filters for Dissected Traffic
Layer filters progressively when investigating:
```
// Step 1: Protocol + namespace
http && dst.pod.namespace == "production"
// Step 2: Add error condition
http && dst.pod.namespace == "production" && status_code >= 500
// Step 3: Narrow to service
http && dst.pod.namespace == "production" && status_code >= 500 && dst.service.name == "payment-service"
// Step 4: Narrow to endpoint
http && dst.pod.namespace == "production" && status_code >= 500 && dst.service.name == "payment-service" && path.contains("/charge")
```
Other common RCA filters:
```
dns && dns_response && status_code != 0 // Failed DNS lookups
src.service.namespace != dst.service.namespace // Cross-namespace traffic
http && elapsed_time > 5000000 // Slow transactions (> 5s)
conn && conn_state == "open" && conn_local_bytes > 1000000 // High-volume connections
```
---
## Combining Both Routes
The two routes are complementary. A common pattern:
1. Start with **Dissection** — let the AI agent search and identify the root cause
2. Once you've pinpointed the problematic workloads, use `list_workloads`
to get their IPs (singular lookup by name+namespace, or filtered scan
by namespace/regex/labels against the snapshot)
3. Switch to **PCAP** — export a filtered PCAP of just those workloads for
Wireshark deep-dive, sharing with the network team, or compliance archival
## Use Cases
### Post-Incident RCA
1. Identify the incident time window from alerts, logs, or user reports
2. Check `get_data_boundaries` — is the window still in raw capture (L4)?
3. Check `get_l7_data_boundaries` — was dissection enabled at that time, and
does the window overlap with the L7 entry range? If `dissection_enabled`
is `false` or the window predates the L7 range, the Dissection route is
limited to whatever entries exist now — falling back to the PCAP route
is often the right call.
4. `create_snapshot` covering the incident window (add 15 minutes buffer)
5. **Dissection route**: `start_snapshot_dissection``get_api_stats`
`list_api_calls``get_api_call` → follow the dependency chain
6. **PCAP route**: `list_workloads``export_snapshot_pcap` with BPF →
hand off to Wireshark or archive
### Other Use Cases
- **Trend analysis** — Take snapshots at regular intervals and compare
`get_api_stats` across them to detect latency drift, error rate changes,
or new service-to-service connections.
- **Forensic preservation** — `create_snapshot` + `upload_snapshot_to_cloud`
for immutable, long-term evidence. Downloadable to any cluster months later.
- **Production-to-local replay** — Upload a production snapshot to cloud,
download it on a local KinD cluster, and investigate safely.
## Setup Reference
For CLI installation, MCP configuration, verification, and troubleshooting,
see `references/setup.md`.

View File

@@ -0,0 +1,70 @@
# Kubeshark MCP Setup Reference
## Installing the CLI
**Homebrew (macOS)**:
```bash
brew install kubeshark
```
**Linux**:
```bash
sh <(curl -Ls https://kubeshark.com/install)
```
**From source**:
```bash
git clone https://github.com/kubeshark/kubeshark
cd kubeshark && make
```
## MCP Configuration
**Claude Desktop / Cowork** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp"]
}
}
}
```
**Claude Code (CLI)**:
```bash
claude mcp add kubeshark -- kubeshark mcp
```
**Without kubectl access** (direct URL mode):
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp", "--url", "https://kubeshark.example.com"]
}
}
}
```
```bash
# Claude Code equivalent:
claude mcp add kubeshark -- kubeshark mcp --url https://kubeshark.example.com
```
## Verification
- Claude Code: `/mcp` to check connection status
- Terminal: `kubeshark mcp --list-tools`
- Cluster: `kubectl get pods -l app=kubeshark-hub`
## Troubleshooting
- **Binary not found** → Install via Homebrew or the install script above
- **Connection refused** → Deploy Kubeshark first: `kubeshark tap`
- **No L7 data** → Check `get_dissection_status` and `enable_dissection`
- **Snapshot creation fails** → Verify raw capture is enabled in Kubeshark config
- **Empty snapshot** → Check `get_data_boundaries` — the requested window may
fall outside available data

View File

@@ -0,0 +1,724 @@
---
name: security-audit
description: >
Kubernetes network security audit skill powered by Kubeshark MCP. Use this skill
whenever the user wants to audit a cluster for security threats, detect compromised
workloads, find malicious traffic patterns, hunt for indicators of compromise (IOCs),
check for data exfiltration, identify C2 (command and control) communication,
detect cryptomining, find lateral movement, discover credential theft attempts,
assess network security posture, or perform threat hunting in Kubernetes.
Also trigger when the user mentions security audit, threat detection, compromise
assessment, vulnerability scan, "is my cluster compromised", "find malicious traffic",
"check for threats", DNS exfiltration, DNS tunneling, port scanning, IMDS access,
reverse shell, crypto miner, MITRE ATT&CK, IOC detection, anomaly detection,
suspicious traffic, rogue workloads, unauthorized access, or any request to
evaluate cluster security through network traffic analysis.
---
# Kubernetes Network Security Audit with Kubeshark MCP
You are a Kubernetes network security specialist. Your job is to systematically
audit cluster traffic for indicators of compromise, malicious behavior, and
security threats — using network traffic as the ground truth.
Network traffic cannot lie. Logs can be tampered with, metrics can be spoofed,
but packets on the wire reveal what workloads actually do — what they connect to,
what protocols they speak, what data they send. Your audit leverages this by
examining DNS queries, HTTP requests, L4 flows, and protocol-level payloads
across every dimension of the MITRE ATT&CK framework.
## Prerequisites
Before starting any audit, verify the environment is ready.
**Tool**: `check_kubeshark_status`
Confirm Kubeshark is deployed and tools are available. You need at minimum:
`list_api_calls`, `list_l4_flows`, `list_workloads`, `get_api_call`.
**KFL requirement**: This skill uses KFL filters for all queries. Before
constructing any filter, load the KFL skill (`skills/kfl/`). KFL is statically
typed — incorrect field names will fail silently. If the KFL skill is not
loaded, only use the exact filter examples shown in this skill.
**KFL error resilience**: If a KFL filter returns `undeclared reference` or
similar errors, **do not give up on that phase**. Fall back to:
1. Port-based filtering: `dst.port == 5432` instead of protocol flags
2. Name-based filtering: `dst.name.contains("db")` or `src.name.contains("pod-name")`
3. Browsing entries with `get_api_call` on IDs from `list_l4_flows`
A KFL error means the filter syntax is wrong, not that the data doesn't exist.
## Audit Methodology
A security audit is NOT an incident investigation. You are not responding to
a known event — you are proactively searching for threats that may be hiding
in normal traffic. This requires a systematic sweep across all threat categories,
not a single focused query.
The audit has **two sections** that run in sequence:
```
SECTION A: Real-Time Analysis → Instant, uses live dissected traffic
SECTION B: Snapshot Deep Dive → Immutable evidence, protocol-level inspection
```
### Why Two Sections?
Kubeshark has two modes of data access:
1. **Real-time dissection** — traffic is dissected as it flows through the
cluster. Provides instant access to L7 data (DNS, HTTP, etc.) that is
already captured and indexed. However, real-time dissection is resource-
intensive and may not be enabled, or may have gaps in coverage.
2. **Snapshots** — immutable captures of raw traffic within a time window.
Must be created explicitly, then dissected separately. Guarantees complete
coverage of all packets in the window, but takes time to create and index.
Section A uses whatever is already available — fast, immediate, but possibly
incomplete. Section B creates snapshots for thorough, evidence-grade analysis.
### Severity Classification
Classify every finding using this framework:
| Severity | Criteria | Examples |
|----------|----------|---------|
| **CRITICAL** | Active data exfiltration, credential theft in progress, confirmed C2 | DNS tunneling, IMDS credential harvest, mining pool connections |
| **HIGH** | Reconnaissance with cluster-wide scope, confirmed unauthorized access | K8s API secret enumeration, port scanning, cluster-admin abuse |
| **MEDIUM** | Suspicious patterns requiring investigation, limited-scope recon | Cross-namespace probes, outdated User-Agents, unusual external connections |
| **LOW** | Anomalies that may be benign, single-instance events | Unknown workloads, new external destinations, noisy but not malicious |
### Timezone
Kubeshark returns timestamps in UTC. Always convert to local time before
presenting to the user. Detect the local timezone at the start (e.g.,
`date +%Z`). Present local time as primary, with UTC in parentheses:
`15:03:22 IST (12:03:22 UTC)`.
**Conversion**: Kubeshark timestamps are Unix milliseconds. To convert:
`ms / 1000` → Unix seconds → datetime → format with timezone offset.
Example: `1778534735974``2026-05-11 14:05:35 PDT (21:05:35 UTC)`.
---
## SECTION A: Real-Time Analysis
**Goal**: Fast initial sweep using live data that's already available. No
waiting for snapshot creation or dissection.
### Step 1: Check What's Available
**Tool**: `check_kubeshark_status`
Confirm Kubeshark is running and which tools are available.
**Tool**: `get_data_boundaries`
Check how far back raw capture data exists. You need this to plan snapshot
creation in Step 3 — call it now so the data is ready when you need it.
**Tool**: `list_workloads` (no snapshot_id — queries live state)
Get the current workload inventory for the target namespace. This returns
pod names, namespaces, and IP addresses. Save the IPs — you'll need them
throughout the audit.
**Note**: `list_workloads` without a `snapshot_id` may fail with some
Kubeshark versions (`snapshot_id is required for filtered listing`). If
this happens, use individual lookups with `name` + `namespace` parameters,
or skip to Step 3 and get the workload inventory from the first snapshot.
### Step 2: Query Live Traffic
In parallel, query the real-time dissected traffic across key dimensions.
Use `list_api_calls` and `list_l4_flows` **without** a `snapshot_id` to
hit the live data.
Run these queries simultaneously:
| Query | KFL Filter | What You're Looking For |
|-------|-----------|------------------------|
| DNS traffic | `dns` | Mining domains, high-entropy subdomains, external resolution, NXDOMAIN flood |
| HTTP traffic | `http` | C2 beaconing, suspicious URLs, external destinations, anomalous headers |
| L4 flows | (via `list_l4_flows`) | External IPs, suspicious ports (3333, 4444), IMDS (169.254.169.254), fan-out patterns |
| PostgreSQL | `postgresql` | SQL injection patterns, sensitive table access |
| Redis | `redis` | Dangerous commands (CONFIG, KEYS, CLIENT LIST) |
Filter by namespace if the user specified one (e.g., `dns && src.pod.namespace == "k8s-mule"`).
**Important**: Real-time dissection may have incomplete data — traffic that
arrived before dissection was enabled, or during gaps in coverage, won't
appear. Treat Section A findings as a fast first pass, not the final word.
### Step 3: Create Snapshots (Sequential — One at a Time)
While analyzing real-time data, begin creating snapshots for Section B.
**CRITICAL: Create snapshots ONE AT A TIME, sequentially.** Kubeshark only
supports one concurrent snapshot download. Parallel creation will cause
failures and data loss. The pattern is:
1. Create snapshot → wait for completion → start dissection → move to next
2. Snapshot creation is fast (seconds). Dissection is slow (minutes).
3. You do NOT need to wait for dissection before creating the next snapshot.
Create the next snapshot while the previous one dissects.
Use the data boundaries from Step 1 (`get_data_boundaries`) to calculate
how many snapshots are needed:
```
total_range_ms = newest_timestamp - oldest_timestamp
window_ms = 240000 # 4 minutes
num_snapshots = ceil(total_range_ms / window_ms)
```
Then create snapshots in **4-minute increments**, starting from the most
recent:
```
Step 1: create_snapshot (now - 4min → now)
→ poll get_snapshot until status == "completed"
→ start_snapshot_dissection
Step 2: create_snapshot (now - 8min → now - 4min)
→ poll get_snapshot until status == "completed"
→ start_snapshot_dissection
Step 3: create_snapshot (now - 12min → now - 8min)
→ poll get_snapshot until status == "completed"
→ start_snapshot_dissection
```
**Polling pattern**: After `create_snapshot`, call `get_snapshot` with the
returned snapshot ID to check status. Repeat until `status == "completed"`.
After `start_snapshot_dissection`, call `get_snapshot_dissection_status`
and check until `progress == 100`.
4-minute windows balance snapshot size (fast to create and dissect) against
coverage (captures threats with sleep cycles up to ~3 minutes). Most attack
patterns in the wild repeat within 30-120 seconds.
**Do not skip this step.** A single short snapshot will miss threats with
longer sleep cycles. The 4-minute windows ensure full coverage.
**Note**: Small snapshots (under ~15 minutes of traffic) often dissect in
seconds rather than minutes. If dissection completes quickly, you can
collapse the phased approach (immediate data first, L7 after) into a
single pass through all phases.
### Step 4: Present Intermediate Results
Present Section A findings to the user as **intermediate results** — clearly
labeled as preliminary:
```
## Intermediate Results (Real-Time Analysis)
⚠️ These findings are based on live dissected traffic, which may have
gaps in coverage. Snapshot analysis is in progress and will provide
the complete, evidence-grade audit.
[findings table and details]
Snapshots are being created and dissected. Full report to follow.
```
This gives the user immediate value while snapshots process. But be explicit:
**the audit is not complete until Section B finishes.**
---
## SECTION B: Snapshot Deep Dive
**Goal**: Systematic, thorough analysis against immutable snapshot data.
This is the evidence-grade section — complete coverage, reproducible results.
**The audit is NOT done until this section completes.** Snapshots must be
created, dissected, and analyzed at L7 before the final report is generated.
Section A may miss traffic that wasn't being dissected in real-time — Section B
captures everything in the raw PCAP buffer, including traffic that real-time
dissection dropped or never saw. Do not skip this section or treat Section A
results as the final word.
### What a Snapshot Gives You
A completed snapshot provides **three independent data sources** — do not
wait for dissection to use the first two:
| Source | Available | Tool | What It Provides |
|--------|-----------|------|-----------------|
| **Workloads & IPs** | Immediately | `list_workloads` with `snapshot_id` | Pod names, namespaces, IPs at capture time |
| **L4 Flows** | Immediately | `list_l4_flows` with `snapshot_id` | TCP/UDP connections: src/dst IPs, ports, bytes, duration |
| **PCAP Export** | Immediately | `export_snapshot_pcap` | Raw packets filtered by BPF expression |
| **L7 Dissection** | After indexing | `list_api_calls`, `get_api_call`, `get_api_stats` | DNS queries, HTTP requests, SQL statements, Redis commands, gRPC methods |
### Audit Flow Per Snapshot
For each 4-minute snapshot, run the full 7-phase sweep. Start with immediate
data while dissection completes:
```
Snapshot ready
├── Start dissection (background)
├── Phase 1: list_workloads (immediate) — workload inventory + IPs
│ export_snapshot_pcap (immediate) — raw packet evidence
├── Phase 3: list_l4_flows (immediate) — external flows, port scanning
├── Phase 4: list_l4_flows (immediate) — lateral movement, fan-out
├── [dissection completes]
├── Phase 2: list_api_calls — DNS threat analysis
├── Phase 5: list_api_calls — protocol abuse (PG, Redis, gRPC)
├── Phase 6: list_api_calls — credential access (IMDS, cloud APIs)
└── Phase 7: correlate all findings
```
Process snapshots in reverse chronological order (most recent first). If the
first snapshot reveals enough threats, you may not need to analyze all of them.
### PCAP for Deep Inspection
PCAP export happens in Phase 1b (immediately after snapshot creation). In
later phases, if a new finding needs deeper packet-level analysis beyond
what `list_api_calls` provides, export additional PCAPs using the workload
IPs collected in Phase 1a:
```
export_snapshot_pcap(snapshot_id, bpf_filter="host <workload_ip>")
```
### Merging Findings Across Snapshots
Threats that appear in multiple snapshots are confirmed persistent. One-time
events in a single snapshot may be transient. Note which findings repeat
across snapshots — persistence is a strong signal of real compromise vs.
a single anomalous event.
---
## Phase 1: Workload Inventory & PCAP Evidence
**Goal**: Identify all active workloads, collect their IPs, and export raw
PCAP evidence — all before dissection completes.
**Data source**: Immediate (no dissection needed).
### 1a: Workload Inventory
**Tool**: `list_workloads` with `snapshot_id`
Query with the target namespace (or all namespaces). The response includes
pod names, namespaces, and **IP addresses at capture time** — these IPs are
critical for building BPF filters in later phases and for correlating L4
flows to workload identities.
For each workload, note:
- Pod name and namespace
- IP address (save these — you'll need them for PCAP export and L4 analysis)
- Whether it's expected (matches known deployments)
**What to flag**:
- Workloads not matching any known Deployment/DaemonSet/StatefulSet
- Pods with names that mimic system components (e.g., `kube-proxy-debug`)
- Unexpected number of replicas or pods in the namespace
### 1b: PCAP Export (Immediate — No Dissection Needed)
**Tool**: `export_snapshot_pcap` with `snapshot_id`
PCAP export is available immediately after snapshot creation — it reads raw
packets, not dissected data. Use it now to preserve evidence and get raw
packet-level visibility before L7 dissection completes.
**Export PCAP for every CRITICAL finding** from Section A's real-time analysis.
Use the workload IPs from 1a to build BPF filters:
```
export_snapshot_pcap(snapshot_id, bpf_filter="host <workload_ip>")
```
This is especially useful for:
- Verifying encrypted C2 (TLS ClientHello SNI inspection)
- Confirming Stratum mining protocol content
- Extracting DNS tunnel payloads at packet level
- Preserving forensic evidence before cluster changes
If Section A identified no CRITICAL findings yet, export a broad PCAP for
the most suspicious workloads based on L4 flow analysis (Phase 3).
---
## Phase 2: DNS Threat Analysis
**Goal**: DNS is the single most reliable indicator of compromise. Every attack
that communicates externally needs DNS resolution. Sweep DNS traffic for all
known threat patterns.
### 2a: External DNS (Non-Cluster Queries)
**Tool**: `list_api_calls` with KFL: `dns`
Examine all DNS queries. Flag anything that is NOT `*.cluster.local` or
`*.svc.cluster.local` — these are external resolutions that reveal what
workloads are reaching out to.
**What to flag**:
| Pattern | Threat | KFL Filter |
|---------|--------|------------|
| Mining pool domains (minexmr, nanopool, mining-pool) | Cryptojacking | `dns && dns_questions.exists(q, q.contains("minexmr"))` |
| High-entropy subdomains (base64-like, >30 chars) | DNS tunneling / exfiltration | `dns` — then inspect subdomain length and entropy |
| DGA patterns (random .com/.net with NXDOMAIN) | C2 beaconing | `dns && dns_response && size(dns_answers) == 0` |
| DoH resolver domains (cloudflare-dns.com, dns.google) | DNS bypass / C2 channel | `dns && dns_questions.exists(q, q.contains("cloudflare-dns"))` |
| Cloud API domains (sts.amazonaws.com, s3.amazonaws.com) | Stolen credential usage | `dns && dns_questions.exists(q, q.contains("amazonaws.com"))` |
| C2/attacker domains (attacker, c2, darknet, exfil) | Command & Control | `dns && dns_questions.exists(q, q.contains("c2"))` |
### 2b: DNS Query Volume and Types
High query volume from a single pod is suspicious. Also check for unusual
record types:
- **TXT queries** to external domains → data exfiltration
- **NULL queries** → DNS tunneling (iodine, dnscat2)
- **AXFR queries** → zone transfer attempts (reconnaissance)
- **SRV queries** to many namespaces → service enumeration
### 2c: NXDOMAIN Ratio
A high NXDOMAIN ratio (>20% of queries) from a single source suggests DGA
beaconing — the malware tries many generated domains, most of which don't exist.
**Tool**: `list_api_calls` with KFL: `dns && dns_response && size(dns_answers) == 0`
Compare the count of failed queries to total queries per source pod.
---
## Phase 3: External Communication
**Goal**: Identify all traffic leaving the cluster. Any pod connecting to
external IPs or domains needs justification.
**Data source**: Immediate (no dissection needed). Use L4 flows first,
then enrich with L7 data from dissection when available.
### 3a: L4 External Flows
**Tool**: `list_l4_flows` with `snapshot_id`
This is available immediately — do not wait for dissection. Use the workload
IPs from Phase 1 to map flows to pod identities.
Look for flows where the destination is NOT a cluster-internal IP (not RFC 1918:
10.x.x.x, 172.16-31.x.x, 192.168.x.x). Every external flow is a potential
exfiltration or C2 channel.
**What to flag**:
| Pattern | Threat | Severity |
|---------|--------|----------|
| Destination 169.254.169.254 | IMDS metadata credential theft | CRITICAL |
| Destination port 3333, 14433, 45700 | Stratum mining protocol | CRITICAL |
| Destination port 4444, 1337 | Reverse shell / backdoor | CRITICAL |
| Persistent connections to single external IP | C2 beaconing | HIGH |
| Large outbound data volume (>1MB) to external | Data exfiltration | HIGH |
| Connections to cloud API endpoints (port 443) | Stolen credential usage | MEDIUM |
### 3b: HTTP External Requests
**Tool**: `list_api_calls` with KFL: `http && !dst.pod.namespace.startsWith("kube")`
Inspect outbound HTTP requests for:
- **Beaconing patterns**: Regular-interval requests to the same external URL
- **Suspicious User-Agents**: `Mozilla/4.0`, `curl/`, empty, or malware-like
- **Suspicious paths**: `/check?s=`, `/beacon`, `/heartbeat`, `/proxy?coin=`
- **Base64 in headers**: Oversized Cookie or custom X-* headers with encoded data
- **gRPC to external**: `Content-Type: application/grpc` to non-cluster destinations
- **WebSocket upgrades**: `Upgrade: websocket` to external hosts (potential mining)
---
## Phase 4: Lateral Movement
**Goal**: Identify pods communicating with services they shouldn't — crossing
namespace boundaries, probing infrastructure, or scanning the network.
**Data source**: L4 flows (immediate) for port scanning detection. L7
dissection (after indexing) for cross-namespace HTTP and API server analysis.
### 4a: Cross-Namespace Traffic
**Tool**: `list_api_calls` with KFL: `src.pod.namespace != dst.pod.namespace`
Most pods should only talk within their namespace (and to kube-system services).
Cross-namespace traffic to unexpected destinations is a lateral movement indicator.
### 4b: Kubernetes API Server Access
**Tool**: `list_api_calls` with KFL: `http && dst.port == 443 && path.startsWith("/api")`
Check what pods are querying the K8s API server and what they're requesting:
| API Path | Threat | Severity |
|----------|--------|----------|
| `/api/v1/secrets` | Secret enumeration | CRITICAL |
| `/api/v1/pods` | Workload discovery | HIGH |
| `/apis/rbac.authorization.k8s.io` | RBAC reconnaissance | HIGH |
| `/api/v1/configmaps` | Config enumeration | MEDIUM |
| `/api/v1/namespaces` | Namespace discovery | MEDIUM |
A pod hitting **multiple** of these paths is performing systematic enumeration,
not legitimate API access. Legitimate workloads typically access 1-2 specific
resources, not sweep across resource types.
### 4c: Port Scanning Detection
**Tool**: `list_l4_flows` with `snapshot_id` (immediate — no dissection needed)
Use the workload IPs from Phase 1 to identify the source pod.
Look for a single source IP with connections to:
- Many distinct destination IPs (>10)
- Many distinct destination ports (>5)
- High connection failure rate (RST/timeout)
This is a textbook port scan pattern.
### 4d: Service Fingerprinting
**Tool**: `list_api_calls` with KFL: `http && (path == "/.env" || path == "/actuator/info" || path == "/server-info" || path == "/version")`
These paths are used for service fingerprinting — mapping what software is
running on internal endpoints. A pod probing multiple services with these
paths is performing reconnaissance.
### 4e: Service Account Permission Audit via Traffic
Cross-reference Phase 4b findings (K8s API traffic) with the source pod's
actual service account to determine if permissions are excessive.
For each pod making API server calls:
1. **Identify the service account**: From the workload inventory or via
`kubectl get pod <name> -n <ns> -o jsonpath='{.spec.serviceAccountName}'`
2. **Check what it accessed**: The API paths from Phase 4b reveal what the
pod actually queried (secrets, pods, RBAC, configmaps)
3. **Compare against expected access**: A `frontend` pod should never hit
`/api/v1/secrets`. A `batch-processor` has no reason to query
`/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`.
**What to flag**:
| Pattern | Threat | Severity |
|---------|--------|----------|
| Pod queries secrets but its SA only needs pod read | Over-privileged SA or stolen token | HIGH |
| Pod hits cluster-wide endpoints (`--all-namespaces` style queries) | Cluster-admin binding | CRITICAL |
| Pod's SA is `default` but makes authenticated API calls | Token mounted unnecessarily | MEDIUM |
| Multiple pods share the same over-privileged SA | Lateral blast radius | HIGH |
This converts a network finding (API traffic volume) into an actionable RBAC
recommendation — telling the user exactly which ClusterRoleBinding to revoke.
### 4f: Cross-Namespace Threat Correlation
When port scanning or lateral movement targets IPs outside the audited
namespace (e.g., IPs in the pod CIDR `10.244.x.x` that don't belong to
any workload in the target namespace), resolve them to identify the
cross-namespace blast radius:
1. Use `list_workloads` (all namespaces) to map destination IPs to pods
2. Identify which namespaces are being probed
3. Flag the scope: "port scan from `k8s-mule/network-diagnostics` is
targeting pods in `default`, `monitoring`, and `kube-system`"
This turns a single-namespace finding into a cluster-wide risk assessment.
---
## Phase 5: Protocol Abuse
**Goal**: Inspect L7 payload content for attack patterns within supported
protocols. This is the phase most often skipped — and where subtle threats hide.
### 5a: PostgreSQL Wire Protocol
**Tool**: `list_api_calls` with KFL: `postgresql`
The `postgresql_query` variable contains the full SQL text. Use it to detect:
| KFL Filter | Threat | Severity |
|------------|--------|----------|
| `postgresql && postgresql_query.contains("UNION SELECT")` | SQL injection | HIGH |
| `postgresql && postgresql_query.contains("pg_shadow")` | Password hash theft | CRITICAL |
| `postgresql && postgresql_query.contains("information_schema")` | Schema enumeration | MEDIUM |
| `postgresql && postgresql_query.contains("TRUNCATE")` | Data destruction | CRITICAL |
| `postgresql && postgresql_query.contains("DROP TABLE")` | Data destruction | CRITICAL |
| `postgresql && !postgresql_success` | Failed queries (may indicate probing) | MEDIUM |
Use `get_api_call` to inspect the full SQL content. Also check `postgresql_user`
— queries from unexpected users are suspicious.
### 5b: Redis Protocol
**Tool**: `list_api_calls` with KFL: `redis`
Use `redis_type` (command verb) and `redis_command` (full command line) to detect:
| KFL Filter | Threat | Severity |
|------------|--------|----------|
| `redis && redis_type == "CONFIG"` | Server config dump/write | HIGH |
| `redis && redis_type == "KEYS"` | Full key enumeration | HIGH |
| `redis && redis_type == "CLIENT"` | Connection enumeration | MEDIUM |
| `redis && redis_type == "DEBUG"` | Debug access | MEDIUM |
| `redis && redis_command.contains("CONFIG SET dir")` | Arbitrary file write (RCE) | CRITICAL |
| `redis && redis_type == "FLUSHALL"` | Data destruction | CRITICAL |
### 5c: gRPC Endpoints
**Tool**: `list_api_calls` with KFL: `grpc`
Use `grpc_method` to inspect method names:
| KFL Filter | Threat | Severity |
|------------|--------|----------|
| `grpc && grpc_method.contains("Reflection")` | API surface enumeration | MEDIUM |
| `grpc && dst.name.contains("attacker")` | Data exfiltration | HIGH |
| `grpc && grpc_status != 0` | Failed gRPC calls (may indicate probing) | LOW |
### 5d: HTTP Request Anomalies
**Tool**: `list_api_calls` with KFL: `http`
Check for:
- **WebSocket upgrades to external hosts**: `Upgrade: websocket` header — potential
mining proxy or persistent C2 channel
- **DNS-over-HTTPS requests**: `accept: application/dns-json` header — DNS bypass
- **AWS Signature headers**: `Authorization: AWS4-HMAC-SHA256` — stolen cloud creds
- **IMDS-specific headers**: `X-aws-ec2-metadata-token-ttl-seconds` — token request
---
## Phase 6: Credential Access
**Goal**: Detect active credential theft — IMDS access, service account abuse,
cloud API exploitation.
### 6a: Instance Metadata Service (IMDS)
**Tool**: `list_api_calls` with KFL: `dst.ip == "169.254.169.254"`
Or use `list_l4_flows` to find connections to 169.254.169.254.
Any pod connecting to this IP is attempting to steal the node's cloud credentials.
Check the HTTP paths:
| Path | What's Being Stolen |
|------|-------------------|
| `/latest/meta-data/iam/security-credentials/` | IAM role name |
| `/latest/meta-data/iam/security-credentials/<role>` | Actual AWS credentials |
| `/latest/dynamic/instance-identity/document` | Instance identity (account ID, region) |
| `/latest/user-data` | Instance bootstrap scripts (may contain secrets) |
| `/latest/api/token` (PUT) | IMDSv2 session token |
### 6b: Service Account Token Exfiltration
Look for HTTP requests where the body or headers contain JWT tokens
(strings starting with `eyJ`). These may be service account tokens being
sent to external endpoints.
---
## Phase 7: Attack Chain Correlation
**Goal**: Connect individual findings into a coherent attack narrative.
After completing phases 1-6, synthesize findings into an attack chain. Real
attacks follow a progression:
```
1. INITIAL ACCESS → How did the attacker get in?
2. RECONNAISSANCE → Port scanning, DNS enumeration, API discovery
3. CREDENTIAL ACCESS → IMDS theft, secret enumeration, token exfil
4. LATERAL MOVEMENT → Cross-namespace probing, SSRF, service scanning
5. EXFILTRATION → DNS tunneling, HTTP exfil, gRPC streaming
6. PERSISTENCE → C2 beaconing, cryptomining (monetization)
```
Map each finding to a stage. If you see findings across multiple stages from
the same namespace or related workloads, you've found a coordinated attack.
### Output Format
Present the audit results as:
1. **Workload inventory** — table of all observed workloads with threat level
2. **Detailed findings** — one section per finding, ordered by severity
3. **Attack chain summary** — if findings correlate, map the kill chain
4. **Immediate actions** — prioritized remediation steps
---
## Audit Report — Two-Stage Delivery
The audit produces **two outputs** — an intermediate report during Section A,
and a final PDF report after Section B completes.
### Stage 1: Intermediate Report (after Section A)
Present findings from real-time analysis directly in the conversation. Clearly
label as preliminary. This gives the user immediate value while snapshots
are being created and dissected.
### Stage 2: Final PDF Report (after Section B)
This is the primary deliverable. It is generated **only after all snapshots
have been dissected and analyzed at L7**. Do not generate the final report
based on Section A alone — that would miss protocol-level threats (SQL
injection, Redis abuse, gRPC exfil) that only appear after dissection.
1. **Write** the report as markdown: `security-audit-<namespace>-<date>.md`
Follow the template in `references/report-template.md` — it defines
the full structure: executive summary, threat table, detailed findings
with evidence, attack chain analysis, detection coverage, and remediation.
2. **Convert to PDF** (in preference order):
```bash
npx md-to-pdf security-audit-<namespace>-<date>.md # Best quality
pandoc security-audit-<namespace>-<date>.md -o security-audit-<namespace>-<date>.pdf
```
If neither tool is available, leave the markdown as the deliverable.
3. **The final report must include findings from both sections** — Section A
(real-time) and Section B (snapshot dissection). Findings confirmed by
both sections are marked with higher confidence. Findings only in
Section B (missed by real-time) should be noted — this reveals gaps
in real-time dissection coverage.
### Key Report Requirements
- **Quote raw evidence** — actual DNS queries, HTTP URLs, SQL statements,
Redis commands. The reader must be able to verify without re-running.
- **Timestamp every finding** — snapshot ID + local time (UTC in parentheses).
- **Specific recommendations** — not "fix RBAC" but "revoke ClusterRoleBinding
`mule-recon-cluster-admin`".
- **Include MITRE ATT&CK IDs** for each finding.
- **Evidence preservation** — list snapshot IDs, recommend cloud storage upload.
---
## What Network Auditing Cannot Detect
Be transparent about blind spots. Network traffic analysis **cannot** detect:
- **Configuration vulnerabilities**: Privileged containers, missing resource
limits, permissive RBAC, hostPath mounts — these are YAML-level issues with
no traffic signature
- **Secrets in environment variables**: Hardcoded credentials don't generate
network traffic until used
- **Image vulnerabilities**: CVEs in container images are not visible on the wire
- **Idle threats**: A malicious pod that hasn't started communicating yet
Recommend `kubectl`-based configuration auditing for these gaps. Network
auditing is the complement, not the replacement, for config-level security
scanning.
## Threat Intelligence Reference
For detailed descriptions of all 22 network-observable threat scenarios with
MITRE ATT&CK mappings and detection guidance, see `references/threat-catalog.md`.

View File

@@ -0,0 +1,64 @@
# KFL Quick Reference: Security Audit Filters
## DNS Threat Hunting
```
dns // All DNS traffic
dns && dns_response && size(dns_answers) == 0 // Failed lookups (NXDOMAIN — no answers)
dns && dns_questions.exists(q, q.contains("minexmr")) // Mining pool DNS
dns && dns_questions.exists(q, q.contains("nanopool")) // Mining pool DNS
dns && dns_questions.exists(q, q.contains("amazonaws")) // Cloud API resolution
dns && dns_questions.exists(q, q.contains("cloudflare-dns")) // DoH bypass
dns && dns_questions.exists(q, q.contains("dns.google")) // DoH bypass
```
## External Communication
```
http && dst.name.contains("attacker") // Known-bad destinations
http && map_get(request.headers, "user-agent", "").contains("Mozilla/4.0") // Suspicious UA
http && map_get(request.headers, "accept", "").contains("dns-json") // DoH requests
http && map_get(request.headers, "upgrade", "") == "websocket" // WebSocket (potential mining)
```
## Lateral Movement
```
src.pod.namespace != dst.pod.namespace // Cross-namespace traffic
http && path.startsWith("/api/v1/secrets") // Secret enumeration
http && path == "/.env" // Service fingerprinting
http && path == "/actuator/info" // Spring Boot fingerprinting
http && path == "/version" // Version fingerprinting
```
## Protocol Inspection
```
postgresql // PostgreSQL wire protocol
postgresql && postgresql_query.contains("UNION SELECT") // SQL injection patterns
postgresql && !postgresql_success // Failed PostgreSQL queries
redis // Redis protocol
grpc // gRPC calls (native detection)
grpc && grpc_method.contains("Reflection") // gRPC reflection enumeration
```
## Credential Theft
```
dst.ip == "169.254.169.254" // IMDS access
http && path.contains("/meta-data/iam") // IAM credential paths
http && map_get(request.headers, "authorization", "").startsWith("AWS4-HMAC-SHA256") // Stolen AWS creds
http && "x-aws-ec2-metadata-token-ttl-seconds" in request.headers // IMDSv2 token request
```
## Resource Hijacking
```
dst.port == 3333 // Stratum mining (standard)
dst.port == 14433 // Stratum mining (alt)
dst.port == 45700 // Stratum mining (alt)
dst.port == 4444 // Reverse shell / backdoor
```
## Per-Namespace Scoping
Add namespace filters to any query above:
```
dns && src.pod.namespace == "k8s-mule" // DNS from specific namespace
http && src.pod.namespace == "k8s-mule" // HTTP from specific namespace
redis && src.pod.namespace == "k8s-mule" // Redis from specific namespace
```

View File

@@ -0,0 +1,102 @@
# Security Audit Report Template
Use this template for the markdown report. Fill in all sections, then convert
to PDF.
```markdown
# Kubernetes Network Security Audit Report
**Cluster**: <cluster name/context>
**Namespace**: <target namespace>
**Date**: <audit date and time, local timezone>
**Audit window**: <start time> — <end time> (<duration>)
**Snapshots analyzed**: <count and IDs>
**Audited by**: Claude Code + Kubeshark MCP
---
## Executive Summary
<2-3 sentence summary: how many threats found, highest severity,
whether an active attack chain was identified, top recommendation>
## Threat Summary
| # | Severity | Workload | Threat | MITRE ATT&CK |
|---|----------|----------|--------|---------------|
| 1 | CRITICAL | log-shipper | DNS Tunneling | T1048.003 |
| 2 | CRITICAL | cloud-health-monitor | IMDS Credential Theft | T1552.005 |
| ... | | | | |
## Detailed Findings
### Finding 1: <Title> (CRITICAL)
**Workload**: <pod name>
**MITRE ATT&CK**: <technique ID and name>
**Snapshot**: <snapshot ID>
**Detection method**: <which phase and tool detected this>
**Evidence**:
<Specific traffic data — DNS queries, HTTP requests, L4 flows,
protocol payloads. Include timestamps, source/dest, and relevant
content. Quote actual query names, URLs, SQL statements, or
Redis commands observed.>
**Impact**:
<What this means — data at risk, credentials exposed, scope of access>
**Recommendation**:
<Specific remediation — NetworkPolicy, RBAC change, pod deletion, credential rotation>
---
(repeat for each finding)
## Attack Chain Analysis
<If findings correlate, map the kill chain:
Initial Access → Reconnaissance → Credential Access → Lateral Movement →
Exfiltration → Persistence. Identify which workloads participate in each stage.>
## Detection Coverage
| Phase | Checked | Findings |
|-------|---------|----------|
| Workload Inventory | Yes | <count> |
| DNS Threat Analysis | Yes | <count> |
| External Communication | Yes | <count> |
| Lateral Movement | Yes | <count> |
| Protocol Abuse | Yes | <count> |
| Credential Access | Yes | <count> |
## Limitations
<What this audit cannot detect — config-level vulnerabilities,
image CVEs, idle threats. Recommend complementary tools.>
## Immediate Actions
1. <Highest priority action>
2. <Second priority>
3. ...
## Evidence Preservation
<List snapshot IDs created during this audit. Recommend uploading
to cloud storage for long-term retention. Include PCAP export
commands for key findings.>
```
## Quality Guidelines
- **Include raw evidence** — quote actual DNS queries, HTTP URLs, SQL
statements, Redis commands. The reader should be able to verify findings
without re-running the audit.
- **Timestamp everything** — every finding should reference the snapshot ID
and timestamp (local time with UTC in parentheses).
- **Be specific in recommendations** — not "fix RBAC" but "revoke
ClusterRoleBinding `mule-recon-cluster-admin` and replace with a
namespace-scoped Role granting only `get` on `pods`".
- **Include MITRE ATT&CK IDs** — makes the report actionable for security
teams that track coverage against the framework.

View File

@@ -0,0 +1,190 @@
# Network Threat Catalog
22 network-observable threat patterns organized by MITRE ATT&CK tactic.
Each entry describes the attack, what it looks like on the wire, and how
to detect it with Kubeshark.
## Command & Control (TA0011)
### DGA Beaconing (T1568.002)
- **What**: Malware generates pseudo-random domain names daily and queries DNS
for each. The C2 operator registers a few; most resolve to NXDOMAIN.
- **Wire signature**: Burst of DNS queries for high-entropy .com/.net domains
with >80% NXDOMAIN response rate.
- **KFL**: `dns && dns_response && size(dns_answers) == 0` — then check for entropy in queried names.
- **Difficulty**: Medium. NXDOMAIN flood is distinctive but low-rate DGA can
blend with legitimate DNS failures.
### HTTP C2 Beaconing (T1071.001)
- **What**: Implant calls home via HTTP GET at regular intervals, receiving
tasking in the response body. Cobalt Strike, Meterpreter pattern.
- **Wire signature**: Periodic HTTP GET to fixed external URL at suspiciously
regular intervals (30-60s). Outdated User-Agent (Mozilla/4.0). Session
identifiers in URL path.
- **KFL**: `http && dst.name.contains("attacker")` or check for User-Agent anomalies.
- **Difficulty**: Medium. Regularity is the key anomaly.
### Encrypted C2 (T1573.002)
- **What**: C2 over HTTPS. Content is encrypted but TLS SNI reveals suspicious
domain names.
- **Wire signature**: Outbound TLS to non-standard domains (darknet, cdn-mirror).
DNS queries preceding the connection reveal the target.
- **KFL**: `dns && (dns_questions.exists(q, q.contains("darknet")) || dns_questions.exists(q, q.contains("cdn-mirror")))`.
- **Difficulty**: Hard. Encrypted, uses standard port 443.
### DNS-over-HTTPS C2 (T1572)
- **What**: Bypasses cluster DNS by sending queries as HTTPS to public DoH
resolvers (cloudflare-dns.com, dns.google). C2 commands embedded in TXT
responses.
- **Wire signature**: HTTP requests to DoH endpoints with `accept: application/dns-json`
header. No corresponding queries on port 53.
- **KFL**: `http && (dst.name.contains("cloudflare-dns") || dst.name.contains("dns.google"))`.
- **Difficulty**: Hard. Looks like regular HTTPS to trusted providers.
## Exfiltration (TA0010)
### DNS Tunneling (T1048.003)
- **What**: Full bidirectional data channel over DNS using tools like iodine,
dnscat2. Data encoded in long subdomain labels.
- **Wire signature**: High-frequency DNS queries (20+/burst) with subdomain
labels near 63-byte limit. Mix of A, TXT, NULL query types.
- **KFL**: `dns && dns_questions.exists(q, q.contains("data-relay"))` or look for
high query rates per source.
- **Difficulty**: Medium. Volume and long subdomains are distinctive.
### HTTP Header Exfiltration (T1048.001)
- **What**: Data exfiltrated in HTTP headers (Cookie, X-Trace-ID) disguised
as analytics tracking. Low volume to evade detection.
- **Wire signature**: HTTP GET to analytics-looking URL with oversized Cookie
or custom headers containing base64-encoded data.
- **KFL**: `http && dst.name.contains("cdn-provider")`.
- **Difficulty**: Hard. Low volume, standard HTTP, looks like analytics.
### DNS Credential Exfiltration (T1048.003)
- **What**: Stolen JWT tokens or credentials encoded in DNS TXT queries to
attacker-controlled authoritative nameserver.
- **Wire signature**: DNS TXT queries with structured multi-label subdomains
containing base64-like encoded data.
- **KFL**: `dns && dns_questions.exists(q, q.contains("steal-creds"))`.
- **Difficulty**: Medium. Multi-label structure is distinctive.
### gRPC Stream Exfiltration (T1048.001)
- **What**: Data exfiltration via gRPC (HTTP/2) POST to external endpoint.
Blends with normal microservice traffic.
- **Wire signature**: HTTP/2 POST with `Content-Type: application/grpc` to
external destination with exfil-related method names.
- **KFL**: `grpc && dst.name.contains("attacker")`.
- **Difficulty**: Hard. gRPC is normal in K8s. External destination is the signal.
## Lateral Movement (TA0008)
### K8s API Enumeration (T1613)
- **What**: Compromised pod uses mounted service account token to enumerate
secrets, pods, RBAC bindings across all namespaces.
- **Wire signature**: HTTPS to kubernetes.default.svc with broad GET requests
across /api/v1/secrets, /pods, /configmaps, /clusterrolebindings.
- **KFL**: `http && dst.port == 443 && path.contains("/api/v1/secrets")`.
- **Difficulty**: Medium. The fanout across resource types is the anomaly.
### SSRF to Internal Services (T1090)
- **What**: Pod probes cross-namespace internal services it shouldn't talk to —
kube-dns metrics, Prometheus, Grafana, dashboards.
- **Wire signature**: HTTP to multiple ClusterIP services across namespaces
from a single source pod.
- **KFL**: `http && src.pod.namespace == "k8s-mule" && dst.pod.namespace != "k8s-mule"`.
- **Difficulty**: Medium. Cross-namespace breadth is the signal.
### Port Scanning (T1046)
- **What**: Sweep of common ports across pod CIDR after initial access.
- **Wire signature**: Rapid TCP SYN from single source to many IPs on ports
80, 443, 3306, 5432, 6379, 8080, 9090, 27017. High RST/timeout rate.
- **KFL**: `tcp && src.name == "network-diagnostics"`.
- **Difficulty**: Easy. Classic scan pattern — high fan-out, high failure rate.
### Service Fingerprinting (T1046)
- **What**: HTTP probes to discovery paths across multiple services to identify
running software.
- **Wire signature**: HTTP GET to /version, /healthz, /.env, /actuator/info,
/server-info. HEAD and OPTIONS methods. Multiple targets from one source.
- **KFL**: `http && (path == "/.env" || path == "/actuator/info")`.
- **Difficulty**: Medium. Path patterns are distinctive.
## Credential Access (TA0006)
### IMDS Metadata Theft (T1552.005)
- **What**: Query AWS/GCP instance metadata to steal IAM role credentials.
The Capital One breach vector.
- **Wire signature**: HTTP to 169.254.169.254 with paths /latest/meta-data/iam/,
/latest/user-data, /latest/api/token (PUT for IMDSv2).
- **KFL**: `dst.ip == "169.254.169.254"`.
- **Difficulty**: Easy. Destination IP is unique and unmistakable.
### Cloud API Abuse (T1078.004)
- **What**: Direct calls to AWS APIs (STS, S3, EC2) with stolen credentials
from a workload pod.
- **Wire signature**: DNS for sts.amazonaws.com, s3.amazonaws.com. HTTPS
requests with AWS Signature V4 Authorization headers.
- **KFL**: `dns && dns_questions.exists(q, q.contains("amazonaws.com"))`.
- **Difficulty**: Medium. Cloud API DNS from a non-controller pod is suspicious.
## Resource Hijacking (TA0040)
### Stratum Mining Protocol (T1496)
- **What**: XMRig/miner connecting to mining pool via Stratum JSON-RPC over TCP.
- **Wire signature**: TCP connection to port 3333/14433/45700 with JSON-RPC
messages: mining.subscribe, mining.authorize, mining.submit.
- **KFL**: `dst.port == 3333`.
- **Difficulty**: Medium. Port 3333 is a well-known mining indicator.
### Mining Pool DNS (T1496)
- **What**: DNS resolution of known mining pool domains before connecting.
- **Wire signature**: DNS queries for domains containing minexmr, nanopool,
mining-pool, hashvault, supportxmr.
- **KFL**: `dns && (dns_questions.exists(q, q.contains("minexmr")) || dns_questions.exists(q, q.contains("mining-pool")))`.
- **Difficulty**: Easy. Mining domain names are unmistakable.
### WebSocket Mining (T1496)
- **What**: Browser-based miner communicating via WebSocket on standard ports.
- **Wire signature**: HTTP Upgrade: websocket request to external host with
mining-related URL path (/proxy?coin=, ?algo=randomx).
- **KFL**: `http && map_get(request.headers, "upgrade", "") == "websocket"`.
- **Difficulty**: Hard. WebSocket on port 80/443 looks normal. Only URL reveals intent.
## Protocol Abuse
### SQL Injection via PG Wire (T1190)
- **What**: SQL injection payloads sent through PostgreSQL wire protocol.
- **Wire signature**: PG protocol carrying UNION SELECT, information_schema,
pg_shadow queries.
- **KFL**: `postgresql`.
- **Difficulty**: Medium. PG dissection reveals the SQL content directly.
### Redis Unauthorized Access (T1190)
- **What**: Unauthenticated Redis instance probed with dangerous commands.
- **Wire signature**: Redis protocol: CONFIG GET *, KEYS *, CLIENT LIST, DEBUG.
- **KFL**: `redis`.
- **Difficulty**: Easy. Redis command names are directly visible.
### Database Destruction (T1485)
- **What**: Ransomware pattern — SELECT * (data theft) then TRUNCATE/DROP (destruction).
- **Wire signature**: PG protocol showing SELECT followed by TRUNCATE on same table.
- **KFL**: `postgresql`.
- **Difficulty**: Medium. DDL commands in PG protocol are visible with dissection.
## Reconnaissance (TA0043)
### DNS Zone Enumeration (T1018)
- **What**: Brute-force DNS queries across namespaces to discover services.
Includes SRV lookups and AXFR zone transfer attempts.
- **Wire signature**: High volume of DNS queries for *.svc.cluster.local patterns
across many namespaces. Many NXDOMAIN responses.
- **KFL**: `dns && src.name == "service-discovery"`.
- **Difficulty**: Easy. Volume and cross-namespace pattern is obvious.
### gRPC Reflection Enumeration (T1046)
- **What**: Probing gRPC server reflection to discover API surfaces without
needing proto files.
- **Wire signature**: HTTP/2 POST to /grpc.reflection.v1alpha.ServerReflection/
ServerReflectionInfo across multiple services.
- **KFL**: `grpc && grpc_method.contains("Reflection")` or `http && path.contains("grpc.reflection")`.
- **Difficulty**: Medium. Reflection path is a known enumeration vector.