Compare commits

..
5 Commits
Author SHA1 Message Date
Volodymyr Stoiko c7fe7f4ce5 helm: render AUTH_GROUP_MAPPING in the hub config-map
The local chart never rendered AUTH_GROUP_MAPPING, so tap.auth.groupMapping
was silently dropped — SSO groups and the kubeshark-cli SA fell back to
AUTH_DEFAULT_ROLE instead of their mapped roles.
2026-06-15 22:56:31 +00:00
Volodymyr Stoiko dba8867fb5 helm: kubeshark-cli ServiceAccount + token-minter RBAC + allowlist (phase 2a)
When tap.auth.cli.enabled, create the kubeshark-cli ServiceAccount and a
Role granting 'create' on serviceaccounts/token for it (bound to
configurable tap.auth.cli.subjects), and set AUTH_CLI_SERVICE_ACCOUNTS so
the Hub allowlists it. Binding the Role to a subject is what grants that
subject CLI access to a gated Hub.
2026-06-15 21:57:28 +00:00
Volodymyr Stoiko 1c3e99d823 cli: authenticate console ws with the ServiceAccount token (phase 2a)
console mints the kubeshark-cli token like the MCP runner and sends it via
X-Kubeshark-Authorization, falling back to License-Key when minting isn't
possible.
2026-06-15 21:57:27 +00:00
Volodymyr Stoiko 7c67abc3fd cli: mint a ServiceAccount token for gated Hub auth (phase 2a)
Provider.MintHubToken requests a short-lived token (audience kubeshark-hub)
for the kubeshark-cli ServiceAccount via the TokenRequest API. The Hub
client now prefers that token via the X-Kubeshark-Authorization custom
header (which survives the kube API-server proxy), falling back to
License-Key when minting isn't possible (--url mode, SA missing, or no
RBAC to mint). The MCP runner mints once at startup.

Pairs with hub branch cli-sa-auth. Needs the helm kubeshark-cli SA + RBAC
and the hub AUTH_CLI_SERVICE_ACCOUNTS allowlist to validate end-to-end.
2026-06-15 21:47:07 +00:00
Volodymyr Stoiko 6ba67ef3f5 cli: authenticate Hub HTTP requests with License-Key (gated-auth phase 1)
Add a License-Key RoundTripper plus NewHubHTTPClient / HubAuthTransport
helpers in utils, and route the MCP runner's Hub clients (connect, --url
validate, file download, tools list) through them so the CLI keeps working
once the Hub enforces auth. License-Key is a custom header, so it survives
the kube API-server service proxy (a bearer token would not). Surface
ErrHubAuthRequired on a 401/302 from the Hub.

console already sends License-Key on its ws connection. pprof is not
covered here: it shells out to 'go tool pprof <hubUrl>' / opens a browser,
external fetches that can't carry a custom header (follow-up).
2026-06-15 20:59:27 +00:00
49 changed files with 274 additions and 680 deletions
-1
View File
@@ -1,6 +1,5 @@
{
"name": "kubeshark",
"owner": { "name": "kubeshark" },
"description": "Kubeshark network observability skills for Kubernetes",
"plugins": [
{
-101
View File
@@ -1,101 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL Advanced"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '16 11 * * 5'
jobs:
analyze:
name: Analyze (${{ matrix.language }})
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners (GitHub.com only)
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: go
build-mode: autobuild
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
# Use `c-cpp` to analyze code written in C, C++ or both
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node`
# or others). This is typically only required for manual builds.
# - name: Setup runtime (example)
# uses: actions/setup-example@v1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# If the analyze step fails for one of the languages you are analyzing with
# "We were unable to automatically build your code", modify the matrix above
# to set the build mode to "manual" for that language. Then modify this step
# to build your code.
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
- name: Run manual build steps
if: matrix.build-mode == 'manual'
shell: bash
run: |
echo 'If you are using a "manual" build mode for one or more of the' \
'languages you are analyzing, replace this with the commands to build' \
'your code, for example:'
echo ' make bootstrap'
echo ' make release'
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
go-version-file: 'go.mod'
- name: Go lint
uses: golangci/golangci-lint-action@v7
uses: golangci/golangci-lint-action@v3
with:
version: v2.8.0
version: latest
args: --timeout=10m
-36
View File
@@ -1,36 +0,0 @@
version: "2"
run:
timeout: 10m
go: "1.25.0"
modules-download-mode: readonly
linters:
default: none
enable:
- govet
- staticcheck
- ineffassign
- unused
formatters:
enable:
- gofmt
- goimports
settings:
gofmt:
simplify: true
goimports:
local-prefixes:
- github.com/kubeshark/kubeshark
issues:
max-issues-per-linter: 0
max-same-issues: 0
output:
formats:
tab:
path: stdout
colors: true
print-linter-name: true
+2 -3
View File
@@ -4,13 +4,12 @@ import (
"fmt"
"github.com/creasty/defaults"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/kubernetes/helm"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var cleanCmd = &cobra.Command{
+1 -2
View File
@@ -8,14 +8,13 @@ import (
"regexp"
"time"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/errormessage"
"github.com/kubeshark/kubeshark/internal/connect"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/misc/fsUtils"
"github.com/rs/zerolog/log"
)
func startProxyReportErrorIfAny(kubernetesProvider *kubernetes.Provider, ctx context.Context, serviceName string, podName string, proxyPortLabel string, srcPort uint16, dstPort uint16, healthCheck string) {
+2 -3
View File
@@ -4,13 +4,12 @@ import (
"fmt"
"github.com/creasty/defaults"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
+4 -15
View File
@@ -12,26 +12,19 @@ import (
"github.com/creasty/defaults"
"github.com/gorilla/websocket"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var consoleCmd = &cobra.Command{
Use: "console",
Short: "Stream the scripting console logs into shell (temporarily non-functional — under refactoring after hub API changes)",
Long: `Stream the scripting console logs into shell.
NOTE: This command is currently non-functional and under refactoring. The hub
moved scripting-console log streaming off the /scripts/logs WebSocket onto a
Connect-RPC streaming service, and this client has not yet been updated to use
it, so no logs will stream until the migration lands.`,
Short: "Stream the scripting console logs into shell",
RunE: func(cmd *cobra.Command, args []string) error {
log.Warn().Msg(fmt.Sprintf(utils.Yellow, "The 'console' command is temporarily non-functional and under refactoring: the hub moved scripting-console log streaming off the /scripts/logs WebSocket onto a Connect-RPC service, and this client has not yet been updated. No logs will stream, so the command exits without doing anything."))
runConsole()
return nil
},
}
@@ -49,7 +42,6 @@ func init() {
consoleCmd.Flags().StringP(configStructs.ReleaseNamespaceLabel, "s", defaultTapConfig.Release.Namespace, "Release namespace of Kubeshark")
}
//nolint:unused // retained for the in-progress console refactoring; re-wired once the Connect-RPC client lands
func runConsoleWithoutProxy() {
log.Info().Msg("Starting scripting console ...")
time.Sleep(5 * time.Second)
@@ -146,10 +138,7 @@ func runConsoleWithoutProxy() {
}
}
//nolint:unused // retained for the in-progress console refactoring; re-wired once the Connect-RPC client lands
func runConsole() {
log.Warn().Msg(fmt.Sprintf(utils.Yellow, "The 'console' command is temporarily non-functional and under refactoring: the hub moved scripting-console log streaming off the /scripts/logs WebSocket onto a Connect-RPC service, and this client has not yet been updated. No logs will stream."))
go runConsoleWithoutProxy()
// Create interrupt channel and setup signal handling once
+1 -2
View File
@@ -3,9 +3,8 @@ package cmd
import (
"fmt"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/spf13/cobra"
)
var licenseCmd = &cobra.Command{
+2 -3
View File
@@ -5,14 +5,13 @@ import (
"fmt"
"github.com/creasty/defaults"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/errormessage"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/misc/fsUtils"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var logsCmd = &cobra.Command{
+1 -4
View File
@@ -1,9 +1,8 @@
package cmd
import (
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/spf13/cobra"
)
var mcpURL string
@@ -11,7 +10,6 @@ var mcpKubeconfig string
var mcpListTools bool
var mcpConfig bool
var mcpAllowDestructive bool
var mcpToken string
var mcpCmd = &cobra.Command{
Use: "mcp",
@@ -120,7 +118,6 @@ 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(&mcpToken, "token", "", "ServiceAccount/bearer token for a gated Hub in --url mode (e.g. from 'kubectl create token kubeshark-cli --audience kubeshark-hub'); also read from KUBESHARK_HUB_TOKEN. Ignored without --url, where the token is minted and auto-renewed from kube access.")
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")
+24 -74
View File
@@ -15,14 +15,13 @@ import (
"sync"
"time"
"github.com/rs/zerolog"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/internal/connect"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog"
)
// MCP Protocol types (JSON-RPC 2.0)
@@ -160,47 +159,34 @@ type mcpServer struct {
cachedHubMCP *hubMCPResponse // Cached tools/prompts from Hub
cachedAt time.Time // When the cache was populated
hubMCPMu sync.Mutex
tokenSource func() string // hub SA token source; proxy mode auto-renews, URL mode is the static --token. nil → License-Key
saToken string // ServiceAccount token for a gated Hub (phase 2a); empty falls back to License-Key
}
const hubMCPCacheTTL = 5 * time.Minute
// hubTokenSource builds the SA-token source used to authenticate to a gated
// Hub. Proxy mode (kube access) returns an auto-renewing minter so long-lived
// processes keep working past the ~1h token TTL; URL mode returns the static
// --token / KUBESHARK_HUB_TOKEN (it can't mint without kube access). Returns
// nil when no token is available, so callers fall back to the License-Key.
func hubTokenSource(urlMode bool) func() string {
if urlMode {
staticTok := mcpToken
if staticTok == "" {
staticTok = os.Getenv("KUBESHARK_HUB_TOKEN")
}
if staticTok == "" {
return nil
}
return func() string { return staticTok }
}
provider, err := getKubernetesProviderForCli(true, true)
if err != nil {
return nil
}
return kubernetes.NewHubTokenRenewer(provider, config.Config.Tap.Release.Namespace).Token
}
func runMCPWithConfig(setFlags []string, directURL string, allowDestructive bool) {
// Disable zerolog output to stderr (MCP uses stdio)
zerolog.SetGlobalLevel(zerolog.Disabled)
urlMode := directURL != ""
// Hub SA-token source: auto-renewing in proxy mode, the static --token in
// URL mode; nil falls back to the License-Key. See hubTokenSource.
tokenSource := hubTokenSource(urlMode)
// Best-effort: mint a scoped ServiceAccount token so the CLI authenticates
// to a gated Hub as kubeshark-cli. Falls back to License-Key when minting
// isn't possible (no kube access in --url mode, SA missing, or no RBAC).
saToken := ""
if !urlMode {
if provider, err := getKubernetesProviderForCli(true, true); err == nil {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
if tok, terr := provider.MintHubToken(ctx, config.Config.Tap.Release.Namespace); terr == nil {
saToken = tok
}
cancel()
}
}
server := &mcpServer{
httpClient: utils.NewHubHTTPClientWithTokenSource(30*time.Second, tokenSource, config.Config.License),
tokenSource: tokenSource,
httpClient: utils.NewHubHTTPClientWithToken(30*time.Second, saToken, config.Config.License),
saToken: saToken,
stdin: os.Stdin,
stdout: os.Stdout,
setFlags: setFlags,
@@ -228,7 +214,7 @@ func (s *mcpServer) validateDirectURL() error {
s.directURL = urlStr
// Use a short timeout for validation
client := utils.NewHubHTTPClientWithTokenSource(10*time.Second, s.tokenSource, config.Config.License)
client := utils.NewHubHTTPClientWithToken(10*time.Second, s.saToken, config.Config.License)
// Try to reach the MCP API base endpoint which returns tool definitions
testURL := fmt.Sprintf("%s/api/mcp", urlStr)
@@ -239,7 +225,7 @@ func (s *mcpServer) validateDirectURL() error {
defer func() { _ = resp.Body.Close() }()
if utils.IsAuthRequired(resp) {
return fmt.Errorf("%s", s.hubAuthErrorMessage())
return utils.ErrHubAuthRequired
}
// Try to parse the MCP response to validate it's a valid Kubeshark endpoint
@@ -319,20 +305,6 @@ func (s *mcpServer) ensureBackendConnection() string {
// fetchHubMCP fetches tools and prompts from the Hub's /api/mcp endpoint
// Returns nil if Hub is not available or returns an error
// hubAuthErrorMessage returns a clear, user-facing explanation for an
// auth-required hub response (401, or an unfollowed SSO 302/303 redirect),
// distinguishing a missing/expired credential from a generic API error. URL
// mode can't auto-renew, so it points the user at re-minting.
func (s *mcpServer) hubAuthErrorMessage() string {
if s.urlMode {
return "Hub requires authentication (401/SSO redirect) and no accepted credential was presented. " +
"In URL mode pass a token via --token / KUBESHARK_HUB_TOKEN (mint with " +
"`kubectl create token kubeshark-cli --audience kubeshark-hub`) — it can't auto-renew, so re-mint and " +
"restart if it expired — or set a valid license (License-Key) if the deployment uses one."
}
return "Hub rejected the request (401 Unauthorized): the minted kubeshark-cli token was not accepted (check the Hub's AUTH_CLI_SERVICE_ACCOUNTS allowlist and the token audience), or no credential was available — if you lack RBAC to mint the token the CLI falls back to the License-Key, so set a valid license."
}
func (s *mcpServer) fetchHubMCP() *hubMCPResponse {
s.hubMCPMu.Lock()
defer s.hubMCPMu.Unlock()
@@ -355,10 +327,6 @@ func (s *mcpServer) fetchHubMCP() *hubMCPResponse {
}
defer func() { _ = resp.Body.Close() }()
if utils.IsAuthRequired(resp) {
fmt.Fprintf(os.Stderr, "[kubeshark-mcp] %s\n", s.hubAuthErrorMessage())
return nil
}
if resp.StatusCode >= 400 {
return nil
}
@@ -815,9 +783,6 @@ func (s *mcpServer) callHubTool(toolName string, args map[string]any) (string, b
return fmt.Sprintf("Error reading response: %v", err), true
}
if utils.IsAuthRequired(resp) {
return s.hubAuthErrorMessage(), true
}
if resp.StatusCode >= 400 {
return fmt.Sprintf("Hub API error (%d): %s", resp.StatusCode, string(body)), true
}
@@ -878,8 +843,7 @@ func (s *mcpServer) callDownloadFile(args map[string]any) (string, bool) {
// The default s.httpClient has a 30s total timeout which would fail for large files (up to 10GB).
// This client sets only connection-level timeouts and lets the body stream without a deadline.
downloadClient := &http.Client{
CheckRedirect: utils.StopOnSSORedirect,
Transport: utils.HubAuthTransportWithTokenSource(s.tokenSource, config.Config.License, &http.Transport{
Transport: utils.HubAuthTransportWithToken(s.saToken, config.Config.License, &http.Transport{
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 30 * time.Second,
}),
@@ -891,11 +855,6 @@ func (s *mcpServer) callDownloadFile(args map[string]any) (string, bool) {
}
defer func() { _ = resp.Body.Close() }()
// Catch auth-required (401 / unfollowed SSO redirect) before treating the
// body as a file, so we don't write an HTML login page to disk.
if utils.IsAuthRequired(resp) {
return s.hubAuthErrorMessage(), true
}
if resp.StatusCode >= 400 {
return fmt.Sprintf("Error downloading file: HTTP %d", resp.StatusCode), true
}
@@ -1123,10 +1082,6 @@ func listMCPTools(directURL string) {
fmt.Println("=========")
fmt.Println()
// Same hub credential as the running server: renewing minter in proxy
// mode, static --token in URL mode (nil → License-Key).
tokenSource := hubTokenSource(directURL != "")
// URL mode - no cluster management, connect directly
if directURL != "" {
fmt.Printf("URL Mode: %s\n\n", directURL)
@@ -1139,7 +1094,7 @@ func listMCPTools(directURL string) {
fmt.Println()
hubURL := strings.TrimSuffix(directURL, "/") + "/api/mcp"
fetchAndDisplayTools(hubURL, 30*time.Second, tokenSource)
fetchAndDisplayTools(hubURL, 30*time.Second)
return
}
@@ -1163,7 +1118,7 @@ func listMCPTools(directURL string) {
}
fmt.Printf("Connected to: %s\n\n", hubURL)
fetchAndDisplayTools(hubURL, 30*time.Second, tokenSource)
fetchAndDisplayTools(hubURL, 30*time.Second)
}
// establishProxyConnection sets up proxy to Kubeshark and returns the hub URL
@@ -1212,8 +1167,8 @@ func establishProxyConnection(timeout time.Duration) (string, error) {
}
// fetchAndDisplayTools fetches tools from the Kubeshark API and displays them
func fetchAndDisplayTools(hubURL string, timeout time.Duration, tokenSource func() string) {
client := utils.NewHubHTTPClientWithTokenSource(timeout, tokenSource, config.Config.License)
func fetchAndDisplayTools(hubURL string, timeout time.Duration) {
client := utils.NewHubHTTPClient(timeout, config.Config.License)
// Fetch tools list from /api/mcp endpoint
resp, err := client.Get(strings.TrimSuffix(hubURL, "/mcp") + "/mcp")
@@ -1223,11 +1178,6 @@ func fetchAndDisplayTools(hubURL string, timeout time.Duration, tokenSource func
}
defer func() { _ = resp.Body.Close() }()
if utils.IsAuthRequired(resp) {
fmt.Println("Kubeshark API: 401 Unauthorized — the Hub requires a valid credential. In --url mode pass --token (or KUBESHARK_HUB_TOKEN); in proxy mode ensure you have RBAC to mint the kubeshark-cli token.")
return
}
// Parse the response using the Hub MCP response format
var mcpInfo hubMCPResponse
if err := json.NewDecoder(resp.Body).Decode(&mcpInfo); err != nil {
+2 -3
View File
@@ -658,10 +658,9 @@ func TestMCP_ToolsList_IncludesFileTools(t *testing.T) {
func TestMCP_FullConversation(t *testing.T) {
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
if r.URL.Path == "/" {
_, _ = w.Write([]byte(`{"name":"hub","tools":[{"name":"list_workloads","description":"","inputSchema":{}}]}`))
case "/tools/call":
} else if r.URL.Path == "/tools/call" {
_, _ = w.Write([]byte(`{"data":"ok"}`))
}
}))
+8 -9
View File
@@ -8,14 +8,13 @@ import (
"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"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"github.com/kubeshark/kubeshark/config/configStructs"
)
// pcapDumpCmd represents the consolidated pcapdump command
@@ -46,12 +45,12 @@ var pcapDumpCmd = &cobra.Command{
// Use the current context in kubeconfig
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
return fmt.Errorf("Error building kubeconfig: %w", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("error creating Kubernetes client: %w", err)
return fmt.Errorf("Error creating Kubernetes client: %w", err)
}
// Parse the `--time` flag
@@ -60,7 +59,7 @@ var pcapDumpCmd = &cobra.Command{
if timeIntervalStr != "" {
duration, err := time.ParseDuration(timeIntervalStr)
if err != nil {
return fmt.Errorf("invalid --time value %q: %w", timeIntervalStr, err)
return fmt.Errorf("Invalid format %w", err)
}
tempCutoffTime := time.Now().Add(-duration)
cutoffTime = &tempCutoffTime
@@ -71,17 +70,17 @@ var pcapDumpCmd = &cobra.Command{
if destDir != "" {
info, err := os.Stat(destDir)
if os.IsNotExist(err) {
return fmt.Errorf("directory does not exist: %s", destDir)
return fmt.Errorf("Directory does not exist: %s", destDir)
}
if err != nil {
return fmt.Errorf("error checking dest directory: %w", err)
return fmt.Errorf("Error checking dest directory: %w", err)
}
if !info.IsDir() {
return fmt.Errorf("dest path is not a directory: %s", destDir)
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)
return fmt.Errorf("Directory %s is not writable", destDir)
}
_ = os.Remove(tempFile.Name())
}
+1 -2
View File
@@ -2,10 +2,9 @@ package cmd
import (
"github.com/creasty/defaults"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config/configStructs"
)
var pprofCmd = &cobra.Command{
+3 -4
View File
@@ -5,13 +5,12 @@ import (
"fmt"
"github.com/go-cmd/cmd"
"github.com/rivo/tview"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/utils"
"github.com/rivo/tview"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
)
func runPprof() {
+1 -2
View File
@@ -2,10 +2,9 @@ package cmd
import (
"github.com/creasty/defaults"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config/configStructs"
)
var proxyCmd = &cobra.Command{
+1 -2
View File
@@ -5,14 +5,13 @@ import (
"fmt"
"net/http"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/internal/connect"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog/log"
)
func runProxy(block bool, noBrowser bool) {
+2 -3
View File
@@ -4,11 +4,10 @@ import (
"fmt"
"github.com/creasty/defaults"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
+4 -5
View File
@@ -12,16 +12,15 @@ import (
"github.com/creasty/defaults"
"github.com/fsnotify/fsnotify"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
)
var scriptsCmd = &cobra.Command{
+2 -3
View File
@@ -4,12 +4,11 @@ import (
"errors"
"github.com/creasty/defaults"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/errormessage"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var tapCmd = &cobra.Command{
+2 -2
View File
@@ -16,12 +16,11 @@ import (
core "k8s.io/api/core/v1"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/config/configStructs"
"github.com/kubeshark/kubeshark/errormessage"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/rs/zerolog/log"
)
const cleanupTimeout = time.Minute
@@ -446,6 +445,7 @@ func postFrontStarted(ctx context.Context, kubernetesProvider *kubernetes.Provid
time.Sleep(100 * time.Millisecond)
}
if (config.Config.Scripting.Source != "" || len(config.Config.Scripting.Sources) > 0) && config.Config.Scripting.WatchScripts {
watchScripts(ctx, kubernetesProvider, false)
}
+2 -3
View File
@@ -5,11 +5,10 @@ import (
"strconv"
"time"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
)
var versionCmd = &cobra.Command{
+3 -4
View File
@@ -13,14 +13,13 @@ import (
"github.com/creasty/defaults"
"github.com/goccy/go-yaml"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/misc/version"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/misc/version"
"github.com/kubeshark/kubeshark/utils"
)
const (
+24 -12
View File
@@ -4,10 +4,9 @@ import (
"os"
"path/filepath"
"github.com/kubeshark/kubeshark/config/configStructs"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/util/homedir"
"github.com/kubeshark/kubeshark/config/configStructs"
)
const (
@@ -103,10 +102,23 @@ func CreateDefaultConfig() ConfigStruct {
},
},
Auth: configStructs.AuthConfig{
RolesClaim: "groups",
DefaultRole: "kubeshark-viewer",
GroupMapping: map[string]string{},
Roles: map[string]configStructs.RoleConfig{},
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,
},
},
},
EnabledDissectors: []string{
"amqp",
@@ -133,15 +145,15 @@ func CreateDefaultConfig() ConfigStruct {
"tcp-conn",
},
PortMapping: configStructs.PortMapping{
HTTP: []uint16{80, 443, 8080},
AMQP: []uint16{5671, 5672},
KAFKA: []uint16{9092},
MONGODB: []uint16{27017},
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},
LDAP: []uint16{389},
DIAMETER: []uint16{3868},
},
Dashboard: configStructs.DashboardConfig{
CompleteStreamingEnabled: true,
+1 -2
View File
@@ -7,9 +7,8 @@ import (
"path/filepath"
"strings"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
)
type ScriptingConfig struct {
+29 -70
View File
@@ -155,6 +155,23 @@ type ProbeConfig struct {
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"`
CanDelete bool `yaml:"canDelete" json:"canDelete" default:"true"`
}
type Role struct {
Filter string `yaml:"filter" json:"filter" default:""`
CanDownloadPCAP bool `yaml:"canDownloadPCAP" json:"canDownloadPCAP" default:"false"`
CanUseScripting bool `yaml:"canUseScripting" json:"canUseScripting" default:"false"`
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"`
@@ -173,70 +190,12 @@ type AuthConfig struct {
// 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"`
RolesClaim string `yaml:"rolesClaim" json:"rolesClaim"`
// DefaultRole is applied when the authenticated user's SSO claim has no
// recognized group. Must be one of the four built-in roles
// (kubeshark-admin / kubeshark-realtime / kubeshark-snapshot /
// kubeshark-viewer), the name of an operator-defined role under
// `tap.auth.roles`, or empty for strict-deny.
DefaultRole string `yaml:"defaultRole" json:"defaultRole"`
// GroupMapping translates SSO group names into role names (built-in or
// operator-defined). Optional — groups whose name already matches a
// built-in role are identity-matched and don't need an entry here.
// Operator-defined role names MUST appear here to participate in
// resolution (identity-match is built-in-only).
GroupMapping map[string]string `yaml:"groupMapping" json:"groupMapping"`
// Roles is the operator-defined role catalogue, keyed by role name.
// Each role has its own capability set + namespace scope. Names with
// the `kubeshark-` prefix are reserved for built-ins and will be
// rejected at hub startup. Unknown capability strings are dropped
// with a warning; empty / "*" namespace specs mean deny-all-data and
// allow-all respectively.
Roles map[string]RoleConfig `yaml:"roles" json:"roles"`
Cli CliAuthConfig `yaml:"cli" json:"cli"`
Saml SamlConfig `yaml:"saml" json:"saml"`
}
// CliAuthConfig gates ServiceAccount-token auth for the CLI. When enabled, the
// chart creates a `kubeshark-cli` ServiceAccount plus a Role permitting
// `create` on its token, and the hub allowlists it via the
// AUTH_CLI_SERVICE_ACCOUNTS env var. The CLI mints a short-lived token for
// that SA to authenticate to a gated hub. Map `kubeshark-cli` to a role via
// GroupMapping (or DefaultRole); without a mapping it falls back to
// DefaultRole.
type CliAuthConfig struct {
Enabled bool `yaml:"enabled" json:"enabled" default:"false"`
// Subjects are the RBAC subjects permitted to mint the kubeshark-cli
// token — i.e. who may use the CLI against a gated hub. Rendered verbatim
// into the kubeshark-cli-token-minter RoleBinding; empty means the Role is
// created but bound to nobody.
Subjects []CliAuthSubject `yaml:"subjects" json:"subjects"`
}
// CliAuthSubject is one entry under tap.auth.cli.subjects, mirroring
// rbacv1.Subject: Kind is User, Group or ServiceAccount; ApiGroup is
// rbac.authorization.k8s.io for User and Group and must be empty for
// ServiceAccount; Namespace applies to ServiceAccount only. The empty-able
// fields are omitted when unset so the rendered RoleBinding stays valid for
// every kind.
type CliAuthSubject struct {
Kind string `yaml:"kind" json:"kind"`
Name string `yaml:"name" json:"name"`
ApiGroup string `yaml:"apiGroup,omitempty" json:"apiGroup,omitempty"`
Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"`
}
// RoleConfig is an operator-defined role declared under tap.auth.roles.
// Capabilities is the closed vocabulary documented in the hub project
// (snapshot:read / snapshot:write / snapshot:dissection / dissection:live /
// dissection:control / pods:target:write / settings:write); unknown
// capability strings are warn-dropped at hub startup. Namespaces is a
// comma-separated list with `*` (allow-all) and glob (`foo-*`, `*-bar`,
// `*mid*`) support; empty string means deny-all-data.
type RoleConfig struct {
Capabilities []string `yaml:"capabilities" json:"capabilities"`
Namespaces string `yaml:"namespaces" json:"namespaces"`
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 {
@@ -333,15 +292,15 @@ type PcapDumpConfig struct {
}
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"`
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"`
LDAP []uint16 `yaml:"ldap" json:"ldap"`
DIAMETER []uint16 `yaml:"diameter" json:"diameter"`
}
type SecurityContextConfig struct {
+1 -1
View File
@@ -148,7 +148,7 @@ func TestMergeSetFlagNotSliceValues(t *testing.T) {
{Name: "int field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6}}},
{Name: "bool field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true}}},
{Name: "uint field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)}}},
{Name: "four fields combined", FieldsSetValues: []FieldSetValues{
{Name: "four fields combined", FieldsSetValues: []FieldSetValues {
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
+19 -19
View File
@@ -1,6 +1,8 @@
module github.com/kubeshark/kubeshark
go 1.25.0
go 1.24.0
toolchain go1.24.5
require (
github.com/creasty/defaults v1.5.2
@@ -36,11 +38,11 @@ require (
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.34 // 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.5.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
@@ -51,7 +53,7 @@ require (
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.1.0 // indirect
github.com/go-logr/logr v1.4.3 // 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.23.0 // indirect
@@ -87,7 +89,7 @@ require (
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/spdystream v0.5.1 // 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
@@ -98,30 +100,28 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.14.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.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/stretchr/testify v1.11.1 // 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
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/term v0.45.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.12.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/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/grpc v1.83.0 // indirect
google.golang.org/protobuf v1.36.11 // 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
@@ -133,7 +133,7 @@ require (
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.2 // 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
+56 -56
View File
@@ -36,8 +36,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/containerd/containerd v1.7.34 h1:Q35B4FUECxcoaMz9QrOlqp+0s72w8/0NWawVMhVdf5g=
github.com/containerd/containerd v1.7.34/go.mod h1:ozI//0TomTCLPhQREnx0IXDIQMg+Fk7yTtg9fNvU8EQ=
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@@ -53,8 +53,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creasty/defaults v1.5.2 h1:/VfB6uxpyp6h0fr7SPp7n8WJBoV8jfxQXPCnkVSjyls=
github.com/creasty/defaults v1.5.2/go.mod h1:FPZ+Y0WNrbqOVw+c6av63eyHUAl6pMHZwqLPvXUZGfY=
github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -101,8 +101,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
@@ -238,8 +238,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -294,8 +294,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
@@ -327,8 +327,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tanqiangyes/grep-go v0.0.0-20220515134556-b36bff9c3d8e h1:+qDZ81UqxfZsWK6Vq9wET3AsdQxHGbViYOqkNxZ9FnU=
github.com/tanqiangyes/grep-go v0.0.0-20220515134556-b36bff9c3d8e/go.mod h1:ANZlXE3vfRYCYnkojePl2hJODYmOeCVD+XahuhDdTbI=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@@ -348,16 +348,16 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
@@ -382,16 +382,16 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsu
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -400,14 +400,14 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -416,18 +416,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -447,31 +447,31 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -480,14 +480,14 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q=
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0=
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -526,8 +526,8 @@ k8s.io/kubectl v0.33.2 h1:7XKZ6DYCklu5MZQzJe+CkCjoGZwD1wWl7t/FxzhMz7Y=
k8s.io/kubectl v0.33.2/go.mod h1:8rC67FB8tVTYraovAGNi/idWIK90z2CHFNMmGJZJ3KI=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go/v2 v2.6.2 h1:N04RXngAp1LJKTG6ifz3xHPipasEkWr+hFmInja5YKo=
oras.land/oras-go/v2 v2.6.2/go.mod h1:PlTtg4JTDJkDe8yVHpM2wz7/YDc00GVas+i4jAW2TZ4=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
-2
View File
@@ -218,8 +218,6 @@ Example for overriding image names:
| `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.cli.enabled` | Enable ServiceAccount-token auth for the CLI. Creates a `kubeshark-cli` ServiceAccount plus a Role permitting `create` on its token, and allowlists it on the hub via `AUTH_CLI_SERVICE_ACCOUNTS`. The CLI mints a short-lived token for that SA to authenticate to a gated hub. Map `kubeshark-cli` to a role via `tap.auth.groupMapping`; without a mapping it falls back to `tap.auth.defaultRole`. | `false` |
| `tap.auth.cli.subjects` | RBAC subjects permitted to mint the `kubeshark-cli` token — i.e. who may use the CLI against a gated hub. Each entry mirrors an `rbacv1.Subject`: `kind` (`User`, `Group` or `ServiceAccount`), `name`, `apiGroup` (`rbac.authorization.k8s.io` for `User`/`Group`, omitted for `ServiceAccount`) and `namespace` (`ServiceAccount` only). Empty means the Role is created but bound to nobody. | `[]` |
| `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`) | `` |
@@ -146,10 +146,6 @@ spec:
value: '{{ (include "sentry.enabled" .) }}'
- name: SENTRY_ENVIRONMENT
value: '{{ .Values.tap.sentry.environment }}'
{{- if (((.Values.tap).auth).enabled) }}
- name: HUB_INTERNAL_TOKEN_PATH
value: /var/run/secrets/kubeshark/hub-token/token
{{- end }}
resources:
limits:
{{ if ne (toString .Values.tap.resources.sniffer.limits.cpu) "0" }}
@@ -239,11 +235,6 @@ spec:
{{- if .Values.tap.persistentStorage }}
subPathExpr: $(NODE_NAME)
{{- end }}
{{- if (((.Values.tap).auth).enabled) }}
- mountPath: /var/run/secrets/kubeshark/hub-token
name: hub-internal-token
readOnly: true
{{- end }}
{{- if .Values.tap.tls }}
- command:
- ./tracer
@@ -439,12 +430,3 @@ spec:
emptyDir:
sizeLimit: {{ .Values.tap.storageLimit }}
{{- end }}
{{- if (((.Values.tap).auth).enabled) }}
- name: hub-internal-token
projected:
sources:
- serviceAccountToken:
path: token
audience: kubeshark-hub
expirationSeconds: 3600
{{- end }}
+2 -2
View File
@@ -29,11 +29,11 @@ data:
{{ (default false .Values.demoModeEnabled) | ternary "default" .Values.tap.auth.type }}
{{- end }}'
AUTH_SAML_IDP_METADATA_URL: '{{ .Values.tap.auth.saml.idpMetadataUrl }}'
AUTH_ROLES: '{{ .Values.tap.auth.roles | toJson }}'
AUTH_CLI_SERVICE_ACCOUNTS: '{{ if (((.Values.tap).auth).cli).enabled }}{{ .Release.Namespace }}:kubeshark-cli{{ end }}'
AUTH_ROLES_CLAIM: '{{ .Values.tap.auth.rolesClaim }}'
AUTH_DEFAULT_ROLE: '{{ default "" .Values.tap.auth.defaultRole }}'
AUTH_GROUP_MAPPING: '{{ default (dict) .Values.tap.auth.groupMapping | toJson }}'
AUTH_ROLES: '{{ default (dict) .Values.tap.auth.roles | toJson }}'
AUTH_GROUP_MAPPING: '{{ .Values.tap.auth.groupMapping | default dict | toJson }}'
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 }}'
+28 -4
View File
@@ -153,12 +153,36 @@ tap:
auth:
enabled: false
type: saml
rolesClaim: groups
defaultRole: kubeshark-viewer
groupMapping: {}
roles: {}
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: ""
# CLI ServiceAccount-token auth (gated-auth phase 2a). When enabled, the
# chart creates a `kubeshark-cli` ServiceAccount and a Role permitting
# `create` on its token, and the Hub allowlists it via
# AUTH_CLI_SERVICE_ACCOUNTS. The CLI mints a short-lived token for that SA
# to authenticate to a gated Hub. Map `kubeshark-cli` to a role via
# `groupMapping` (or `defaultRole`); without a mapping it falls back to
# `defaultRole`.
cli:
enabled: false
# RBAC subjects allowed to mint the kubeshark-cli token — i.e. who may
# use the CLI against a gated Hub. Example:
# - kind: User
# name: alice@example.com
# apiGroup: rbac.authorization.k8s.io
subjects: []
saml:
idpMetadataUrl: ""
+5 -13
View File
@@ -230,9 +230,7 @@ func TestMCP_ToolsList_ReadOnly(t *testing.T) {
}
var result struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
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)
@@ -264,9 +262,7 @@ func TestMCP_ToolsList_WithDestructive(t *testing.T) {
}
var result struct {
Tools []struct {
Name string `json:"name"`
} `json:"tools"`
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)
@@ -300,9 +296,7 @@ func TestMCP_CheckKubesharkStatus_NotRunning(t *testing.T) {
}
var result struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
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)
@@ -347,10 +341,8 @@ func TestMCP_StartKubeshark_WithoutFlag(t *testing.T) {
resp := session.callTool(t, 2, "start_kubeshark", nil)
var result struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
IsError bool `json:"isError"`
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)
+1 -1
View File
@@ -51,7 +51,7 @@ func (connector *Connector) TestConnection(path string) error {
}
if retriesLeft == 0 {
return fmt.Errorf("couldn't reach the URL: %s after %d retries", connector.url, connector.retries)
return fmt.Errorf("Couldn't reach the URL: %s after %d retries!", connector.url, connector.retries)
}
return nil
}
+2 -3
View File
@@ -6,12 +6,11 @@ import (
"slices"
"strings"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
)
const (
+4 -5
View File
@@ -8,6 +8,8 @@ import (
"regexp"
"strings"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"helm.sh/helm/v3/pkg/action"
@@ -20,9 +22,6 @@ import (
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/repo"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
)
const ENV_HELM_DRIVER = "HELM_DRIVER"
@@ -80,7 +79,7 @@ func (h *Helm) Install() (rel *release.Release, err error) {
}
var cp string
cp, err = client.LocateChart(chartURL, settings)
cp, err = client.ChartPathOptions.LocateChart(chartURL, settings)
if err != nil {
return
}
@@ -88,7 +87,7 @@ func (h *Helm) Install() (rel *release.Release, err error) {
m := &downloader.Manager{
Out: os.Stdout,
ChartPath: cp,
Keyring: client.Keyring,
Keyring: client.ChartPathOptions.Keyring,
SkipUpdate: false,
Getters: getter.All(settings),
RepositoryConfig: settings.RepositoryConfig,
+2 -70
View File
@@ -3,8 +3,6 @@ package kubernetes
import (
"context"
"fmt"
"sync"
"time"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -19,13 +17,6 @@ const (
CLIServiceAccountName = "kubeshark-cli"
hubTokenExpirySeconds = 3600
// hubTokenRenewMargin re-mints this long before expiry so requests in
// flight always carry a comfortably valid token.
hubTokenRenewMargin = 5 * time.Minute
// hubTokenMintRetryInterval throttles re-minting after a failure so a
// cluster where minting can't succeed (no RBAC, SA missing) doesn't pay a
// blocking CreateToken round-trip on every request.
hubTokenMintRetryInterval = 30 * time.Second
)
// MintHubToken requests a short-lived ServiceAccount token (audience
@@ -34,14 +25,6 @@ const (
// kube RBAC must permit `create` on serviceaccounts/token for that SA — which
// is what gates who may use the CLI against a gated Hub.
func (provider *Provider) MintHubToken(ctx context.Context, namespace string) (string, error) {
tok, _, err := provider.mintHubToken(ctx, namespace)
return tok, err
}
// mintHubToken is MintHubToken plus the token's expiry (from the TokenRequest
// response, so a cluster that caps the duration below the request is respected;
// falls back to the requested TTL if the API omits it).
func (provider *Provider) mintHubToken(ctx context.Context, namespace string) (string, time.Time, error) {
exp := int64(hubTokenExpirySeconds)
tr, err := provider.clientSet.CoreV1().ServiceAccounts(namespace).CreateToken(
ctx,
@@ -55,58 +38,7 @@ func (provider *Provider) mintHubToken(ctx context.Context, namespace string) (s
metav1.CreateOptions{},
)
if err != nil {
return "", time.Time{}, fmt.Errorf("minting hub token for serviceaccount %s/%s: %w", namespace, CLIServiceAccountName, err)
return "", fmt.Errorf("minting hub token for serviceaccount %s/%s: %w", namespace, CLIServiceAccountName, err)
}
expireAt := tr.Status.ExpirationTimestamp.Time
if expireAt.IsZero() {
expireAt = time.Now().Add(time.Duration(hubTokenExpirySeconds) * time.Second)
}
return tr.Status.Token, expireAt, nil
}
// HubTokenRenewer hands out a hub SA token, transparently re-minting it before
// it expires. Safe for concurrent use; intended for long-lived CLI processes
// (mcp/console proxy mode) that would otherwise 401 after the ~1h token TTL.
type HubTokenRenewer struct {
provider *Provider
namespace string
mu sync.Mutex
token string
expireAt time.Time
nextAttempt time.Time // earliest time to retry minting after a failure
}
// NewHubTokenRenewer builds a renewer that mints kubeshark-cli tokens in the
// given namespace.
func NewHubTokenRenewer(provider *Provider, namespace string) *HubTokenRenewer {
return &HubTokenRenewer{provider: provider, namespace: namespace}
}
// Token returns a currently-valid hub token, minting or re-minting as needed.
// On a mint failure it returns the last token (possibly "") so the caller can
// fall back to the License-Key rather than hard-failing.
func (r *HubTokenRenewer) Token() string {
r.mu.Lock()
defer r.mu.Unlock()
needsMint := r.token == "" || time.Now().After(r.expireAt.Add(-hubTokenRenewMargin))
if needsMint && time.Now().After(r.nextAttempt) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
tok, expireAt, err := r.provider.mintHubToken(ctx, r.namespace)
cancel()
if err != nil {
// Throttle retries so a failing cluster doesn't block every request
// on a doomed mint; fall through to the expiry check below.
r.nextAttempt = time.Now().Add(hubTokenMintRetryInterval)
} else {
r.token, r.expireAt, r.nextAttempt = tok, expireAt, time.Time{}
}
}
// Never hand out an already-expired token: return "" so the auth
// round-tripper falls back to the License-Key instead of looping on 401s
// (it prefers any non-empty SA token over the License-Key).
if r.token != "" && !r.expireAt.IsZero() && time.Now().After(r.expireAt) {
return ""
}
return r.token
return tr.Status.Token, nil
}
+1 -1
View File
@@ -16,7 +16,7 @@ type PodWatchHelper struct {
func NewPodWatchHelper(kubernetesProvider *Provider, NameRegexFilter *regexp.Regexp) *PodWatchHelper {
return &PodWatchHelper{
kubernetesProvider: kubernetesProvider,
NameRegexFilter: NameRegexFilter,
NameRegexFilter: NameRegexFilter,
}
}
+4 -5
View File
@@ -11,6 +11,10 @@ import (
"regexp"
"strings"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/semver"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog/log"
"github.com/tanqiangyes/grep-go/reader"
core "k8s.io/api/core/v1"
@@ -21,11 +25,6 @@ import (
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/semver"
"github.com/kubeshark/kubeshark/utils"
)
type Provider struct {
+2 -3
View File
@@ -11,13 +11,12 @@ import (
"strings"
"time"
"github.com/kubeshark/kubeshark/config"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubectl/pkg/proxy"
"github.com/kubeshark/kubeshark/config"
)
const k8sProxyApiPrefix = "/"
@@ -156,7 +155,7 @@ func getHttpDialer(kubernetesProvider *Provider, namespace string, podName strin
clientConfigHostUrl, err := url.Parse(kubernetesProvider.clientConfig.Host)
if err != nil {
return nil, fmt.Errorf("failed parsing client config host URL %s, error %w", kubernetesProvider.clientConfig.Host, err)
return nil, fmt.Errorf("Failed parsing client config host URL %s, error %w", kubernetesProvider.clientConfig.Host, err)
}
path := fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/portforward", clientConfigHostUrl.Path, namespace, podName)
+2 -2
View File
@@ -7,10 +7,9 @@ import (
"sync"
"time"
"github.com/kubeshark/kubeshark/debounce"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/watch"
"github.com/kubeshark/kubeshark/debounce"
)
type EventFilterer interface {
@@ -48,6 +47,7 @@ func FilteredWatch(ctx context.Context, watcherCreator WatchCreator, targetNames
case <-ctx.Done():
return
default:
break
}
if err != nil {
+1 -2
View File
@@ -5,10 +5,9 @@ import (
"strconv"
"time"
"github.com/kubeshark/kubeshark/cmd"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/cmd"
)
func main() {
-25
View File
@@ -88,30 +88,6 @@ Connect directly to an existing Kubeshark deployment:
}
```
For a **gated Hub** (`AUTH_ENABLED=true`), URL mode can't mint a token (no kube
access), so supply one explicitly via `--token` (or the `KUBESHARK_HUB_TOKEN`
env var). Mint it from a machine that has cluster access:
```bash
kubectl create token kubeshark-cli -n <release-namespace> --audience kubeshark-hub
```
```json
{
"mcpServers": {
"kubeshark": {
"command": "kubeshark",
"args": ["mcp", "--url", "https://kubeshark.example.com", "--token", "<token>"]
}
}
}
```
The token is short-lived (~1h) and URL mode **cannot auto-renew** it; when it
expires the server reports a clear `401 ... token expired/invalid` message — re-mint
and restart. Proxy mode (default, with kube access) mints the `kubeshark-cli`
token automatically and **auto-renews** it, so long-running sessions don't expire.
#### With Destructive Operations
```json
@@ -203,7 +179,6 @@ Found 5 services connecting to postgres:5432:
| Option | Description |
|--------|-------------|
| `--url` | Direct URL to Kubeshark Hub |
| `--token` | Hub SA/bearer token for `--url` mode against a gated Hub (also `KUBESHARK_HUB_TOKEN`); ignored in proxy mode, which mints and auto-renews the token |
| `--kubeconfig` | Path to kubeconfig file |
| `--allow-destructive` | Enable start/stop operations |
| `--list-tools` | List available tools and exit |
+2 -3
View File
@@ -7,11 +7,10 @@ import (
"os"
"regexp"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/config"
"github.com/kubeshark/kubeshark/kubernetes"
"github.com/kubeshark/kubeshark/misc"
"github.com/rs/zerolog/log"
)
func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath string, grep string) error {
@@ -22,7 +21,7 @@ func DumpLogs(ctx context.Context, provider *kubernetes.Provider, filePath strin
}
if len(pods) == 0 {
return fmt.Errorf("no %s pods found in namespace %s", misc.Software, config.Config.Tap.Release.Namespace)
return fmt.Errorf("No %s pods found in namespace %s", misc.Software, config.Config.Tap.Release.Namespace)
}
newZipFile, err := os.Create(filePath)
+1 -2
View File
@@ -8,10 +8,9 @@ import (
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/kubeshark/kubeshark/misc"
"github.com/kubeshark/kubeshark/utils"
"github.com/rs/zerolog/log"
"github.com/google/go-github/v37/github"
)
+16 -70
View File
@@ -29,14 +29,10 @@ var ErrHubAuthRequired = errors.New("hub requires authentication: set a valid li
// ServiceAccount token (CLI_AUTH_HEADER) when present, falling back to the
// License-Key (admin/transitional). Both are custom headers so they survive
// the kube API-server service proxy, unlike an Authorization bearer.
//
// The SA token is sourced via saTokenFunc on every request rather than captured
// once, so a caller with cluster access (proxy mode) can hand in a renewing
// source and long-lived clients keep working past the token's ~1h expiry.
type hubAuthRoundTripper struct {
saTokenFunc func() string
licenseKey string
base http.RoundTripper
saToken string
licenseKey string
base http.RoundTripper
}
func (rt *hubAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
@@ -44,15 +40,11 @@ func (rt *hubAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
if base == nil {
base = http.DefaultTransport
}
saToken := ""
if rt.saTokenFunc != nil {
saToken = rt.saTokenFunc()
}
switch {
case saToken != "":
case rt.saToken != "":
if req.Header.Get(CLI_AUTH_HEADER) == "" {
req = req.Clone(req.Context())
req.Header.Set(CLI_AUTH_HEADER, saToken)
req.Header.Set(CLI_AUTH_HEADER, rt.saToken)
}
case rt.licenseKey != "":
if req.Header.Get(LICENSE_KEY_HEADER) == "" {
@@ -63,60 +55,22 @@ func (rt *hubAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
return base.RoundTrip(req)
}
// staticToken adapts a fixed token string to the saTokenFunc source, returning
// nil for an empty token so the round-tripper falls back to the License-Key.
func staticToken(saToken string) func() string {
if saToken == "" {
return nil
}
return func() string { return saToken }
}
// StopOnSSORedirect is an *http.Client CheckRedirect that does NOT follow
// SSO-style auth redirects (302 Found / 303 See Other) — it returns the
// redirect response so callers can detect auth-required via IsAuthRequired
// instead of silently following it to an HTML login page (and, for downloads,
// writing that page to disk). Other redirects (301/307/308) are still followed.
func StopOnSSORedirect(req *http.Request, _ []*http.Request) error {
if req.Response != nil {
switch req.Response.StatusCode {
case http.StatusFound, http.StatusSeeOther:
return http.ErrUseLastResponse
}
}
return nil
}
// NewHubHTTPClient returns an *http.Client that authenticates to the Hub with
// the License-Key header.
// the License-Key header (Phase 1).
func NewHubHTTPClient(timeout time.Duration, licenseKey string) *http.Client {
return &http.Client{
Timeout: timeout,
CheckRedirect: StopOnSSORedirect,
Transport: &hubAuthRoundTripper{licenseKey: licenseKey},
Timeout: timeout,
Transport: &hubAuthRoundTripper{licenseKey: licenseKey},
}
}
// NewHubHTTPClientWithToken returns an *http.Client that authenticates to the
// Hub with a fixed ServiceAccount token when saToken is set, otherwise the
// Hub with a ServiceAccount token when saToken is set, otherwise the
// License-Key.
func NewHubHTTPClientWithToken(timeout time.Duration, saToken, licenseKey string) *http.Client {
return &http.Client{
Timeout: timeout,
CheckRedirect: StopOnSSORedirect,
Transport: &hubAuthRoundTripper{saTokenFunc: staticToken(saToken), licenseKey: licenseKey},
}
}
// NewHubHTTPClientWithTokenSource is NewHubHTTPClientWithToken with a token
// source consulted per request, so a renewing source keeps the client
// authenticated past the token's expiry. A nil source falls back to the
// License-Key.
func NewHubHTTPClientWithTokenSource(timeout time.Duration, saTokenFunc func() string, licenseKey string) *http.Client {
return &http.Client{
Timeout: timeout,
CheckRedirect: StopOnSSORedirect,
Transport: &hubAuthRoundTripper{saTokenFunc: saTokenFunc, licenseKey: licenseKey},
Timeout: timeout,
Transport: &hubAuthRoundTripper{saToken: saToken, licenseKey: licenseKey},
}
}
@@ -127,24 +81,16 @@ func HubAuthTransport(licenseKey string, base http.RoundTripper) http.RoundTripp
return &hubAuthRoundTripper{licenseKey: licenseKey, base: base}
}
// HubAuthTransportWithToken is HubAuthTransport with a fixed ServiceAccount
// token (preferred over the License-Key when set).
// HubAuthTransportWithToken is HubAuthTransport with a ServiceAccount token
// (preferred over the License-Key when set).
func HubAuthTransportWithToken(saToken, licenseKey string, base http.RoundTripper) http.RoundTripper {
return &hubAuthRoundTripper{saTokenFunc: staticToken(saToken), licenseKey: licenseKey, base: base}
}
// HubAuthTransportWithTokenSource is HubAuthTransportWithToken with a token
// source consulted per request (see NewHubHTTPClientWithTokenSource).
func HubAuthTransportWithTokenSource(saTokenFunc func() string, licenseKey string, base http.RoundTripper) http.RoundTripper {
return &hubAuthRoundTripper{saTokenFunc: saTokenFunc, licenseKey: licenseKey, base: base}
return &hubAuthRoundTripper{saToken: saToken, licenseKey: licenseKey, base: base}
}
// IsAuthRequired reports whether the response indicates the Hub demanded
// authentication — a 401, or a 302/303 redirect to an SSO login page (the
// hub clients are configured not to follow those; see StopOnSSORedirect).
// authentication — a 401, or a 302 redirect to an SSO login page.
func IsAuthRequired(resp *http.Response) bool {
return resp != nil && (resp.StatusCode == http.StatusUnauthorized ||
resp.StatusCode == http.StatusFound || resp.StatusCode == http.StatusSeeOther)
return resp != nil && (resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusFound)
}
// Get - When err is nil, resp always contains a non-nil resp.Body.
+1 -1
View File
@@ -8,7 +8,7 @@ import (
)
func UnescapeUnicodeCharacters(raw string) string {
str, err := strconv.Unquote(strings.ReplaceAll(strconv.Quote(raw), `\\u`, `\u`))
str, err := strconv.Unquote(strings.Replace(strconv.Quote(raw), `\\u`, `\u`, -1))
if err != nil {
log.Error().Err(err).Send()
return raw