diff --git a/config/configStructs/tapConfig.go b/config/configStructs/tapConfig.go index db48085fd..bb7ea155c 100644 --- a/config/configStructs/tapConfig.go +++ b/config/configStructs/tapConfig.go @@ -175,12 +175,17 @@ type AuthConfig struct { // 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"` + // DefaultRole is applied when a caller has no recognized group, and also + // when Enabled is false — with no authentication there is no identity, + // but there is still a question of what an unidentified caller may do. + // Must be one of the four built-in roles (kubeshark-admin / + // kubeshark-realtime / kubeshark-snapshot / kubeshark-viewer) or the name + // of an operator-defined role under `tap.auth.roles`. + // + // With Enabled true, empty means strict-deny. With Enabled false, empty + // or unrecognized falls back to kubeshark-admin so an install that never + // configured authorization keeps working. + DefaultRole string `yaml:"defaultRole" json:"defaultRole" default:"kubeshark-admin"` // 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. diff --git a/helm-chart/README.md b/helm-chart/README.md index 4283ce15f..4dade67ee 100644 --- a/helm-chart/README.md +++ b/helm-chart/README.md @@ -211,12 +211,12 @@ Example for overriding image names: | `tap.tolerations.workers` | Tolerations for workers components | `[ {"operator": "Exists", "effect": "NoExecute"}` | | `tap.tolerations.hub` | Tolerations for hub component | `[]` | | `tap.tolerations.front` | Tolerations for front-end component | `[]` | -| `tap.auth.enabled` | Enable authentication | `false` | +| `tap.auth.enabled` | Enable authentication. When `false` the Hub identifies nobody, but `tap.auth.defaultRole` is still applied and enforced, so a deployment can be read-only without configuring an identity provider. | `false` | | `tap.auth.type` | Authentication backend. Valid values: `saml`, `oidc` (generic OIDC — Dex, Okta, Auth0, Keycloak, Azure AD, Google, …), `dex` (permanent alias of `oidc`), `descope`, `default` (also routes to Descope). **Breaking**: prior releases routed `oidc` to Descope — if you were using it for Descope, switch to `descope` or `default`. | `saml` | | `tap.auth.approvedEmails` | List of approved email addresses for authentication | `[]` | | `tap.auth.approvedDomains` | List of approved email domains for authentication | `[]` | | `tap.auth.rolesClaim` | Name of the JWT claim (OIDC) or SAML attribute carrying role memberships. | `role` | -| `tap.auth.defaultRole` | Optional role name inside `tap.auth.roles` applied as fallback when an authenticated user has no matching role. Empty string = no fallback, zero-valued permissions. | `""` | +| `tap.auth.defaultRole` | Role applied when a caller has no matching role. Respected whether or not `tap.auth.enabled` is set: with authentication off it is the role every caller gets, so `kubeshark-viewer` yields a read-only deployment with no login. One of the built-in roles (`kubeshark-admin`, `kubeshark-realtime`, `kubeshark-snapshot`, `kubeshark-viewer`) or a role name from `tap.auth.roles`. With authentication on, an empty string means no fallback and therefore no permissions; with it off, an empty or unrecognized value falls back to `kubeshark-admin` so an install that never configured authorization keeps working. | `kubeshark-admin` | | `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. | `[]` | diff --git a/helm-chart/templates/NOTES.txt b/helm-chart/templates/NOTES.txt index dc124a820..574c52356 100644 --- a/helm-chart/templates/NOTES.txt +++ b/helm-chart/templates/NOTES.txt @@ -26,9 +26,15 @@ Your deployment has been successful. The release is named `{{ .Release.Name }}` Notices: {{- if not .Values.tap.auth.enabled }} - API AUTHENTICATION IS DISABLED (tap.auth.enabled=false, the default). +- Callers are not identified, and every one of them gets the role + `{{ default "kubeshark-admin" .Values.tap.auth.defaultRole }}`. +{{- if not (eq (default "kubeshark-admin" .Values.tap.auth.defaultRole) "kubeshark-admin") }} +- That role is enforced, so anyone reaching the Hub is limited to it. +{{- else }} - Anyone able to reach the Hub can read captured traffic and change settings. -- Enable it with `--set tap.auth.enabled=true` plus a `tap.auth.type` of `saml` or `oidc`, - and assign roles via `tap.auth.defaultRole` / `tap.auth.groupMapping`. +- For a read-only deployment with no login, set `tap.auth.defaultRole=kubeshark-viewer`. +{{- end }} +- To identify callers, set `tap.auth.enabled=true` with a `tap.auth.type` of `saml` or `oidc`. - See https://docs.kubeshark.com/en/roles {{- else }} - API authentication is enabled (tap.auth.type={{ .Values.tap.auth.type }}). diff --git a/helm-chart/tests/auth_gating_test.yaml b/helm-chart/tests/auth_gating_test.yaml index 9f5b00af3..5b6588d62 100644 --- a/helm-chart/tests/auth_gating_test.yaml +++ b/helm-chart/tests/auth_gating_test.yaml @@ -116,6 +116,32 @@ tests: path: data.AUTH_TYPE value: descope + ## AUTH_DEFAULT_ROLE decides what a caller with no recognized group may do, + ## including a caller on a deployment with no authentication at all. Its + ## default is admin, so an ungated install behaves as it always has; a + ## narrower value yields a read-only deployment with no identity provider + ## and no login. + + - it: defaults to admin, so an ungated install is unrestricted + asserts: + - equal: + path: data.AUTH_ENABLED + value: "false" + - equal: + path: data.AUTH_DEFAULT_ROLE + value: kubeshark-admin + + - it: carries a narrowed default role with auth off + set: + tap.auth.defaultRole: kubeshark-viewer + asserts: + - equal: + path: data.AUTH_ENABLED + value: "false" + - equal: + path: data.AUTH_DEFAULT_ROLE + value: kubeshark-viewer + ## Role resolution inputs reach the Hub untouched, so `defaultRole` means ## what it says for every auth type. diff --git a/helm-chart/tests/auth_notes_test.yaml b/helm-chart/tests/auth_notes_test.yaml new file mode 100644 index 000000000..a935f5264 --- /dev/null +++ b/helm-chart/tests/auth_notes_test.yaml @@ -0,0 +1,66 @@ +## The install notes are where an operator learns what their auth settings +## actually did. A deployment can now be ungated, or read-only with no login +## at all, and the difference must be stated rather than inferred. +suite: auth install notes +templates: + - templates/NOTES.txt +tests: + - it: warns that a default install is open to anyone who can reach the Hub + asserts: + - matchRegex: + path: raw + pattern: API AUTHENTICATION IS DISABLED + - matchRegex: + path: raw + pattern: every one of them gets the role\n `kubeshark-admin` + - matchRegex: + path: raw + pattern: Anyone able to reach the Hub can read captured traffic + - matchRegex: + path: raw + pattern: set `tap\.auth\.defaultRole=kubeshark-viewer` + + # A narrowed default role is enforced, so the notes must not repeat the + # "anyone can change settings" warning that applies to the admin default. + - it: reports a narrowed default role as enforced + set: + tap.auth.defaultRole: kubeshark-viewer + asserts: + - matchRegex: + path: raw + pattern: every one of them gets the role\n `kubeshark-viewer` + - matchRegex: + path: raw + pattern: That role is enforced + - notMatchRegex: + path: raw + pattern: Anyone able to reach the Hub can read captured traffic + + - it: reports the configured backend when authentication is on + set: + tap.auth.enabled: true + tap.auth.type: oidc + tap.auth.oidc.issuer: https://issuer.example.com + tap.auth.defaultRole: kubeshark-viewer + asserts: + - matchRegex: + path: raw + pattern: API authentication is enabled \(tap\.auth\.type=oidc\) + - matchRegex: + path: raw + pattern: resolve to `kubeshark-viewer` + - notMatchRegex: + path: raw + pattern: API AUTHENTICATION IS DISABLED + + # Empty defaultRole means strict-deny once callers are identified, and the + # notes should say so rather than naming a role nobody configured. + - it: reports deny-all when authentication is on with no default role + set: + tap.auth.enabled: true + tap.auth.type: descope + tap.auth.defaultRole: "" + asserts: + - matchRegex: + path: raw + pattern: resolve to `no role, deny-all` diff --git a/helm-chart/tests/auth_validation_test.yaml b/helm-chart/tests/auth_validation_test.yaml index e3cf6d2aa..12dd67aa4 100644 --- a/helm-chart/tests/auth_validation_test.yaml +++ b/helm-chart/tests/auth_validation_test.yaml @@ -42,6 +42,34 @@ tests: path: data.AUTH_TYPE value: oidc + # Descope carries its own configuration, so there is nothing to require. + - it: accepts descope without extra settings + set: + tap.auth.enabled: true + tap.auth.type: descope + asserts: + - equal: + path: data.AUTH_TYPE + value: descope + + - it: accepts dex with an issuer + set: + tap.auth.enabled: true + tap.auth.type: dex + tap.auth.oidc.issuer: https://dex.example.com + asserts: + - equal: + path: data.AUTH_TYPE + value: dex + + - it: rejects dex without an issuer + set: + tap.auth.enabled: true + tap.auth.type: dex + asserts: + - failedTemplate: + errorPattern: tap.auth.oidc.issuer is empty + # Incomplete auth settings are only a problem when auth is on. The default # install ships tap.auth.type=saml with no IdP and must still render. - it: ignores incomplete auth settings while auth is off @@ -52,3 +80,14 @@ tests: - equal: path: data.AUTH_TYPE value: saml + + - it: ignores incomplete oidc settings while auth is off + set: + tap.auth.type: oidc + asserts: + - equal: + path: data.AUTH_ENABLED + value: "false" + - equal: + path: data.AUTH_TYPE + value: oidc diff --git a/helm-chart/tests/worker_hub_token_test.yaml b/helm-chart/tests/worker_hub_token_test.yaml index fe44dc7d5..628c3b627 100644 --- a/helm-chart/tests/worker_hub_token_test.yaml +++ b/helm-chart/tests/worker_hub_token_test.yaml @@ -70,3 +70,27 @@ tests: mountPath: /var/run/secrets/kubeshark/hub-token name: hub-internal-token readOnly: true + + # The projection is independent of everything that used to feed the old + # auth expression, not just of tap.auth.enabled. + - it: projects the token regardless of licensing or demo mode + set: + license: ABC + demoModeEnabled: true + cloudLicenseEnabled: false + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: hub-internal-token + projected: + sources: + - serviceAccountToken: + path: token + audience: kubeshark-hub + expirationSeconds: 3600 + - contains: + path: spec.template.spec.containers[0].env + content: + name: HUB_INTERNAL_TOKEN_PATH + value: /var/run/secrets/kubeshark/hub-token/token diff --git a/helm-chart/values.yaml b/helm-chart/values.yaml index ef2c03dba..188e42953 100644 --- a/helm-chart/values.yaml +++ b/helm-chart/values.yaml @@ -154,7 +154,7 @@ tap: enabled: false type: saml rolesClaim: groups - defaultRole: kubeshark-viewer + defaultRole: kubeshark-admin groupMapping: {} roles: {} cli: diff --git a/manifests/complete.yaml b/manifests/complete.yaml index 2ccf2287c..cb9c7e187 100644 --- a/manifests/complete.yaml +++ b/manifests/complete.yaml @@ -274,7 +274,7 @@ data: AUTH_SAML_IDP_METADATA_URL: '' AUTH_CLI_SERVICE_ACCOUNTS: '' AUTH_ROLES_CLAIM: 'groups' - AUTH_DEFAULT_ROLE: 'kubeshark-viewer' + AUTH_DEFAULT_ROLE: 'kubeshark-admin' AUTH_GROUP_MAPPING: '{}' AUTH_ROLES: '{}' AUTH_OIDC_ISSUER: 'not set'