From 28079745c7def75fbe72483ea49f7110ea1d9743 Mon Sep 17 00:00:00 2001 From: Volodymyr Stoiko Date: Tue, 14 Jul 2026 18:44:57 +0000 Subject: [PATCH] Merge branch 'master' into gated-auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conflicts were in the helm chart's auth config, where master's permissions refactor (fcceed23) reordered the AUTH_* config-map keys and replaced the inline tap.auth.roles tree with roles/rolesClaim/defaultRole/groupMapping — directly alongside gated-auth's new CLI ServiceAccount-token auth. Neither side removed the other's work, so both are kept. Also promote tap.auth.cli to a real config field. It had been hand-written into helm-chart/values.yaml, which is generated by `make generate-helm-values` (bin/kubeshark__ config > helm-chart/values.yaml). The next regeneration would have silently dropped the key, leaving 22-cli-auth.yaml unable to render the ServiceAccount. Add CliAuthConfig/CliAuthSubject to AuthConfig and regenerate values.yaml from source, so the block now round-trips. Subject fields carry omitempty so User/Group entries don't emit an empty namespace and ServiceAccount entries don't emit an empty apiGroup, keeping the rendered RoleBinding idiomatic. values.yaml is generated and cannot hold comments, so the tap.auth.cli docs live in helm-chart/README.md alongside the other tap.auth.* rows. Drop the internal plan phase labels from the CLI and utils comments; phases exist only in local planning docs. --- cmd/mcpRunner.go | 2 +- config/configStructs/tapConfig.go | 30 ++++++++++++++++++++++++++++++ helm-chart/README.md | 2 ++ helm-chart/values.yaml | 12 ------------ utils/http.go | 2 +- 5 files changed, 34 insertions(+), 14 deletions(-) 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,