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,