diff --git a/cmd/mcpRunner.go b/cmd/mcpRunner.go
index 6c76100b8..42b23b917 100644
--- a/cmd/mcpRunner.go
+++ b/cmd/mcpRunner.go
@@ -159,7 +159,7 @@ 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 (phase 2a); proxy mode auto-renews, URL mode is the static --token. nil → License-Key
+ tokenSource func() string // hub SA token source; proxy mode auto-renews, URL mode is the static --token. nil → License-Key
}
const hubMCPCacheTTL = 5 * time.Minute
diff --git a/config/configStructs/tapConfig.go b/config/configStructs/tapConfig.go
index 238a32e00..758331eb6 100644
--- a/config/configStructs/tapConfig.go
+++ b/config/configStructs/tapConfig.go
@@ -194,9 +194,39 @@ type AuthConfig struct {
// 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 /
diff --git a/helm-chart/README.md b/helm-chart/README.md
index ba6636d59..4283ce15f 100644
--- a/helm-chart/README.md
+++ b/helm-chart/README.md
@@ -218,6 +218,8 @@ 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
(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509crt` | A self-signed X.509 `.cert` contents
(effective, if `tap.auth.type = saml`) | `` |
| `tap.auth.saml.x509key` | A self-signed X.509 `.key` contents
(effective, if `tap.auth.type = saml`) | `` |
diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml
index cdebae536..ef2c03dba 100644
--- a/helm-chart/values.yaml
+++ b/helm-chart/values.yaml
@@ -157,20 +157,8 @@ tap:
defaultRole: kubeshark-viewer
groupMapping: {}
roles: {}
- # 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: ""
diff --git a/utils/http.go b/utils/http.go
index 2f71b731d..c185332e9 100644
--- a/utils/http.go
+++ b/utils/http.go
@@ -88,7 +88,7 @@ func StopOnSSORedirect(req *http.Request, _ []*http.Request) error {
}
// NewHubHTTPClient returns an *http.Client that authenticates to the Hub with
-// the License-Key header (Phase 1).
+// the License-Key header.
func NewHubHTTPClient(timeout time.Duration, licenseKey string) *http.Client {
return &http.Client{
Timeout: timeout,