Compare commits

...

39 Commits

Author SHA1 Message Date
Chris Farhood 876fb062fe fix: restore kube-system for sealed-secrets-controller refs
Reverts docs changes from 143b2c3 that incorrectly replaced
kube-system with headlamp for sealed-secrets-controller commands.

The sealed-secrets-controller runs in kube-system, NOT headlamp.
Only the Headlamp app install namespace was changed to headlamp.

Changes:
- Revert -n headlamp → -n kube-system in all sealed-secrets-controller
  kubectl commands across all docs files
- Revert sealed-secrets-controller.kube-system DNS reference
- Revert --controller-namespace=headlamp → --controller-namespace=kube-system
- Revert 'namespace headlamp' → 'namespace kube-system' in error messages

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 21:30:34 +00:00
Chris Farhood 143b2c36e0 docs: redirect Headlamp install namespace from kube-system to headlamp
Updates install docs, READMEs, troubleshooting guides, and CI/CD tutorial
to reference Headlamp's own install namespace (headlamp) instead of
kube-system for where the Headlamp plugin/UI is installed.

Out-of-scope (left unchanged):
- Source code references to kube-system (controller workload location)
- Test files with kube-system in mock configs

Files changed:
- docs/getting-started/installation.md
- docs/getting-started/quick-start.md
- docs/troubleshooting/README.md
- docs/troubleshooting/common-errors.md
- docs/troubleshooting/controller-issues.md
- docs/troubleshooting/encryption-failures.md
- docs/troubleshooting/permission-errors.md
- docs/tutorials/ci-cd-integration.md
- docs/development/workflow.md

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-04 07:44:22 +00:00
privilegedescalation-engineer[bot] ecdee4a95a Regenerate lockfile for lodash+vite overrides (#53)
Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-04 03:24:04 +00:00
privilegedescalation-engineer[bot] 0c2132b013 fix: update vite to >=6.4.2 to patch arbitrary file read vulnerability (#51)
Vite versions >=6.0.0 <=6.4.1 are vulnerable to arbitrary file read via
the Vite Dev Server WebSocket (server.fs.deny bypass with queries).

CVE: GHSA-p9ff-h696-f583

Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 17:44:05 +00:00
privilegedescalation-engineer[bot] 780f58f9d9 release: v1.0.2 (#50)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-15 04:01:28 +00:00
privilegedescalation-ceo[bot] d1ea2fa36e fix: correct artifacthub-pkg.yml checksum on main for v1.0.1
Co-authored-by: privilegedescalation-ceo[bot] <269721483+privilegedescalation-ceo[bot]@users.noreply.github.com>
2026-04-15 03:51:04 +00:00
privilegedescalation-engineer[bot] 9b385b95a3 fix: pass pr_number input to dual-approval-check workflow (#44)
The dual-approval workflow was not re-triggering on pull_request_review events because the shared workflow was using github.event.pull_request.number which is not available in workflow_call context.

This change explicitly passes the pr_number from the pull_request event to the reusable workflow.

Co-authored-by: Hugh Hackman <hugh@privilegedescalation.dev>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 03:29:59 +00:00
privilegedescalation-ceo[bot] 395ff7de0b chore: add repository_dispatch trigger for automated release 2026-04-15 02:54:37 +00:00
privilegedescalation-ceo[bot] 6aa2fb9c5a Merge pull request #47 from privilegedescalation/release-v1.0.1
Bump to v1.0.1 — fix ArtifactHub checksum
2026-04-15 02:22:41 +00:00
Gandalf the Greybeard ba6ddc1366 Fix node-forge to ^1.4.0 (patch security vulnerabilities)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 00:58:16 +00:00
Gandalf the Greybeard 6f1163c1b8 Regenerate pnpm-lock.yaml with node-forge 1.4.0
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 00:57:58 +00:00
Gandalf the Greybeard 949ce18b12 Set archive-checksum for v1.0.1 tarball
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 00:54:06 +00:00
Gandalf the Greybeard af87036ef0 Fix package.json formatting - restore proper indentation
The package.json was accidentally minified to a single line. This change
restores the standard formatted version with proper 2-space indentation.
2026-04-15 00:46:57 +00:00
privilegedescalation-engineer[bot] e05423f853 Bump to v1.0.1 — fix ArtifactHub checksum
Bumps version to 1.0.1 and updates artifacthub-pkg.yml with the
correct archive URL for v1.0.1. The archive-checksum is intentionally
left blank so the release workflow can compute it after rebuilding the
tarball (fixes the v1.0.0 ordering bug fixed in PR #80).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 00:46:32 +00:00
privilegedescalation-engineer[bot] 2c17512372 fix: update node-forge to 1.4.0 to patch security vulnerabilities (#46)
Resolves 4 high-severity vulnerabilities in node-forge:
- GHSA-2328-f5f3-gj25: basicConstraints bypass
- GHSA-q67f-28xg-22rw: signature forgery Ed25519
- GHSA-5m6q-g25r-mvwx: Denial of Service via Infinite Loop
- GHSA-ppp5-5v6c-4jwp: signature forgery RSA-PKCS

Fixes PRI-21

Co-authored-by: Pawla Abdul (Bot) <pawla@groombook.dev>
2026-04-15 00:14:40 +00:00
privilegedescalation-engineer[bot] 2798bca085 fix: set correct archive checksum for v1.0.0
Co-authored-by: Gandalf the Greybeard <gandalf@privilegedescalation.github>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-03-25 12:15:03 +00:00
privilegedescalation-ceo[bot] 01ebdcfbb1 Merge pull request #39 from privilegedescalation/fix/regenerate-pnpm-lockfile
fix: regenerate pnpm-lock.yaml to include @playwright/test
2026-03-24 23:52:43 +00:00
Gandalf the Greybeard d20e18f13b fix: regenerate pnpm-lock.yaml to include @playwright/test
pnpm-lock.yaml was not updated when @playwright/test@^1.58.2 was added to
package.json, causing CI to fail with ERR_PNPM_OUTDATED_LOCKFILE. This
lockfile-only change resolves that breakage.

Closes https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/issues/38

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 23:40:35 +00:00
privilegedescalation-ceo[bot] f09376020c Merge pull request #37 from privilegedescalation/feat/playwright-e2e-smoke-tests
feat: add Playwright E2E smoke tests
2026-03-24 23:29:14 +00:00
Gandalf the Greybeard a2ac69c764 feat: add Playwright E2E smoke tests
Follows the pattern established in headlamp-intel-gpu-plugin (PR #25):
- e2e/sealed-secrets.spec.ts: 5 smoke tests covering sidebar navigation,
  list view, sealing keys view, cross-view navigation, and plugin settings
- e2e/auth.setup.ts: shared OIDC + token auth setup
- playwright.config.ts: fail-fast if HEADLAMP_URL not set (no prod URL fallback)
- scripts/deploy-e2e-headlamp.sh: ConfigMap-based plugin injection to privilegedescalation-dev
- scripts/teardown-e2e-headlamp.sh: clean teardown of all E2E resources
2026-03-24 23:19:20 +00:00
privilegedescalation-ceo[bot] 4f474e02bc Merge pull request #36 from privilegedescalation/fix/add-package-manager-field
fix: add packageManager field to package.json
2026-03-24 22:45:12 +00:00
privilegedescalation-ceo[bot] 84f0384a2a Merge pull request #35 from privilegedescalation/release/v1.0.0
release: v1.0.0
2026-03-24 22:36:21 +00:00
github-actions[bot] c223d924bc release: v1.0.0 2026-03-24 22:30:53 +00:00
Gandalf the Greybeard 2d7b73466a fix: add packageManager field to package.json
pnpm/action-setup@v5 requires either a version key in the action config
or a packageManager field in package.json. Add the field to unblock the
release workflow.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 22:12:39 +00:00
privilegedescalation-ceo[bot] b15967a9f4 Merge pull request #31 from privilegedescalation/release/v1.0.0
release: sealed-secrets v1.0.0
2026-03-24 22:01:15 +00:00
Gandalf the Greybeard a7adee4e54 fix(ci): remove typescript from overrides, keep only as devDep
npm/pnpm rejects a package.json that specifies the same package in both
overrides and devDependencies (EOVERRIDE). Since typescript is now a
direct devDependency pinned at ~5.6.2, remove it from overrides.
2026-03-24 21:41:08 +00:00
Gandalf the Greybeard 5c420e58a4 fix(ci): add typescript as explicit devDependency
pnpm strict hoisting means only direct deps are on PATH. The overrides
entry pins the version but does not install tsc as a binary. Without an
explicit devDependency entry pnpm run tsc fails with "tsc: not found".
2026-03-24 21:38:23 +00:00
Gandalf the Greybeard 71649454c9 fix(ci): add missing eslint/prettier devDeps, fix tsconfig types
- Add eslint@^8.57.0, @headlamp-k8s/eslint-config@^0.6.0, prettier@^2.8.8
  as explicit devDependencies — without these the lint and format:check CI
  steps fail with "eslint: not found" / "prettier: not found"
- Remove vite/client and vite-plugin-svgr/client from tsconfig types — these
  are transitive deps that pnpm does not hoist; polaris plugin omits them too
  and tsc passes cleanly without them
- Update pnpm-lock.yaml to reflect new direct deps
2026-03-24 21:36:04 +00:00
Gandalf the Greybeard 2234e2878f release: prepare v1.0.0
- Bump version to 1.0.0 in package.json and artifacthub-pkg.yml
- Add explicit devDependencies: vitest, @testing-library/react,
  @testing-library/jest-dom, @testing-library/user-event, jsdom,
  react, react-dom, @types/react, @types/react-dom, react-router-dom,
  @mui/material, notistack — resolves phantom-dep test failures
- Add process.env.NODE_ENV define to vitest.config.mts (fixes
  "act() not supported in production builds" failures)
- Switch to pnpm lockfile (pnpm-lock.yaml), drop package-lock.json
- Remove install-plugin.sh (violates ArtifactHub-only install policy)
- Fill in CHANGELOG entries for v0.2.22, v0.2.23, v0.2.24
- Update CHANGELOG [1.0.0] and version comparison links
- All 233 tests pass

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 21:25:26 +00:00
privilegedescalation-ceo[bot] b3f31e9b76 Merge pull request #30 from privilegedescalation/feat/renovate-extend-org-config
feat: extend Renovate config from org-level preset
2026-03-24 18:46:02 +00:00
Hugh Hackman 68cdb804e8 feat: extend Renovate config from org-level preset
Replaces the duplicated Renovate config with a simple extend from the
org-level preset (privilegedescalation/.github:renovate-config). All
rules (schedule, pinDigests, npm/github-actions minor+patch+major groups)
are now inherited from the org config, which was updated in PR #66 to add
major-version update rules for GitHub Actions.

This eliminates config drift between repos and reduces maintenance toil —
future rule changes only need to be made in one place.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-24 16:16:41 +00:00
privilegedescalation-ceo[bot] b0ad6573d9 Merge pull request #29 from privilegedescalation/chore/renovate-pin-digests
chore(renovate): add pinDigests for GitHub Actions SHA pinning
2026-03-22 11:06:38 +00:00
privilegedescalation-engineer[bot] a6a3cb27fb chore(renovate): add pinDigests to ensure SHA pinning for GitHub Actions
The org renovate-config.json (PR #63) adds pinDigests: true at the org level,
but this repo extends config:recommended directly. Adding pinDigests: true here
ensures GitHub Actions are pinned to full commit SHAs regardless of whether the
org config is extended.

Related: privilegedescalation/.github#63, PRI-757
2026-03-22 07:16:08 +00:00
privilegedescalation-ceo[bot] 724541c329 Merge pull request #28 from privilegedescalation/feat/dual-approval-status-check
ci: add dual-approval status check (CTO + QA)
2026-03-22 04:12:37 +00:00
privilegedescalation-engineer[bot] f5c78ddb9c ci: add dual-approval caller workflow
Calls the shared privilegedescalation/.github dual-approval-check
reusable workflow to enforce CTO + QA approval as a GitHub status check.

Once privilegedescalation/.github#47 is merged, this status check can
be added to required_status_checks in branch protection.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 23:55:49 +00:00
privilegedescalation-ceo[bot] 33a834cd1f Merge pull request #27 from privilegedescalation/fix/artifacthub-metadata-install-methods
fix: update ArtifactHub metadata - remove non-ArtifactHub install methods
2026-03-21 07:36:50 +00:00
Gandalf the Greybeard 0f46892d75 fix: update artifacthub-pkg.yml - remove non-ArtifactHub install methods
- Replace NPM and build-from-source install options with Headlamp native
  plugin installer instructions (Settings → Plugin Catalog)
- Reconcile appVersion (0.36.1 → 0.24.0) to match containersImages ref
- Add changes block documenting v1.0 features for ArtifactHub changelog

Closes privilegedescalation/headlamp-sealed-secrets-plugin#26 (partial)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-03-21 03:17:28 +00:00
privilegedescalation-paperclip[bot] 5d296d9c72 ci: pass GitHub App token secrets to release workflow (#24)
The shared release workflow now requires RELEASE_APP_ID and
RELEASE_APP_PRIVATE_KEY secrets for PR creation, since the org
blocks GITHUB_TOKEN from creating PRs.

Depends on privilegedescalation/.github#31

Co-authored-by: privilegedescalation-paperclip[bot] <268365651+privilegedescalation-paperclip[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 13:24:42 +00:00
privilegedescalation-paperclip[bot] 1b4fe0a8b2 Merge pull request #23 from privilegedescalation/release/v0.2.24
release: v0.2.24
2026-03-19 21:50:49 +00:00
19 changed files with 12763 additions and 18407 deletions
+20
View File
@@ -0,0 +1,20 @@
name: Dual Approval (CTO + QA)
# Calls the shared dual-approval-check workflow.
# Passes when both privilegedescalation-cto and privilegedescalation-qa
# have approved the PR. Add "Dual Approval (CTO + QA)" to required_status_checks
# in branch protection to enforce this gate.
on:
pull_request_review:
types: [submitted, dismissed]
pull_request:
branches: [main]
types: [opened, reopened, synchronize]
jobs:
dual-approval:
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
secrets: inherit
with:
pr_number: ${{ github.event.pull_request.number }}
+7 -2
View File
@@ -7,6 +7,8 @@ on:
description: 'Release version (e.g. 1.0.0)'
required: true
type: string
repository_dispatch:
types: [release]
permissions:
contents: write
@@ -15,6 +17,9 @@ permissions:
jobs:
release:
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
secrets:
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
with:
version: ${{ inputs.version }}
upstream-repo: 'bitnami-labs/sealed-secrets'
version: ${{ inputs.version || github.event.client_payload.version }}
+6
View File
@@ -23,3 +23,9 @@ Thumbs.db
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# E2E
.env.e2e
e2e/.auth/state.json
playwright-report/
test-results/
+48 -2
View File
@@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [1.0.0] - 2026-03-24
### Added
- Explicit `vitest`, `@testing-library/react`, `@testing-library/jest-dom`, `jsdom`, `react`, and `react-dom` devDependencies so tests run reliably without relying on transitive hoisting
### Changed
- Bump to v1.0.0 — stable public API, comprehensive test coverage, ArtifactHub-only installation
### Fixed
- Removed `install-plugin.sh` custom install script in compliance with ArtifactHub-only installation policy
## [0.2.24] - 2026-03-19
### Fixed
- Added npm overrides for `tar` (>=7.5.11) and `undici` (>=7.24.3) to resolve security advisories
- Added `pull-requests: write` permission to release workflow to unblock PR creation
### Changed
- Added ArtifactHub-only installation policy (INSTALLATION_POLICY.md)
- Removed manual install instructions from README
- Dependency bumps: `tar` 7.5.7→7.5.11, `undici` 7.14.0→7.24.4, `rollup` 4.46.3→4.59.0, `minimatch` 3.1.2→3.1.5, `qs` 6.14.1→6.15.0, `storybook` 9.1.17→9.1.20
## [0.2.23] - 2026-03-09
### Changed
- Internal release-pipeline stabilization (re-release of v0.2.22 fixes)
## [0.2.22] - 2026-03-09
### Added
- Architecture decision records for error boundaries and hooks architecture
### Fixed
- Removed remaining `any` types, dead code, and unused exports; added comprehensive tests
- Added missing `archive-checksum` annotation to `artifacthub-pkg.yml`
- Upstream `appVersion` tracking in release workflow (automatically syncs sealed-secrets controller version)
- Package renamed to `headlamp-sealed-secrets` on ArtifactHub for discoverability
- Added `FUNDING.yml` and Apache-2.0 `LICENSE` file
### Changed
- Enhanced Renovate configuration
## [0.2.21] - 2026-03-04
### Added
@@ -126,11 +168,15 @@ Version 0.2.3 was published but with checksum mismatch on Artifact Hub. Supersed
- Dependencies: node-forge for cryptography
- Compatible with Headlamp v0.13.0+
[Unreleased]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.21...HEAD
[Unreleased]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.24...v1.0.0
[0.2.24]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.23...v0.2.24
[0.2.23]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.22...v0.2.23
[0.2.22]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.21...v0.2.22
[0.2.21]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/compare/v0.2.20...v0.2.21
[0.1.0]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.1.0
[0.2.4]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.2.4
[0.2.3]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.2.3
[0.2.2]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.2.2
[0.2.1]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.2.1
[0.2.0]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.2.0
[0.1.0]: https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/tag/v0.1.0
+18 -21
View File
@@ -1,6 +1,6 @@
# Artifact Hub package metadata file
# https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-pkg.yml
version: "0.2.24"
version: "1.0.2"
name: headlamp-sealed-secrets
displayName: Sealed Secrets
createdAt: "2026-02-12T00:00:00Z"
@@ -19,8 +19,8 @@ keywords:
- encryption
- security
annotations:
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/download/v0.2.24/sealed-secrets-0.2.24.tar.gz"
headlamp/plugin/archive-checksum: sha256:c17cf3bed967062c3d364092af4637026f1e2628774f4c6242e0309d78133a25
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/releases/download/v1.0.2/sealed-secrets-1.0.2.tar.gz"
headlamp/plugin/archive-checksum: sha256:0eaf34d380d133120d3a50c890e0c96b23717427887b1f23377a841cb3783b11
headlamp/plugin/version-compat: ">=0.13.0"
headlamp/plugin/distro-compat: "desktop,in-cluster,web,docker-desktop"
links:
@@ -35,31 +35,19 @@ install: |
### Prerequisites
1. Headlamp v0.13.0 or later
1. [Headlamp](https://headlamp.dev) v0.13.0 or later
2. Sealed Secrets controller installed on your cluster:
```bash
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
```
### Install the Plugin
### Install via Headlamp Plugin Catalog
#### Option 1: From NPM
```bash
npm install -g headlamp-sealed-secrets
```
1. Open Headlamp and navigate to **Settings → Plugin Catalog**
2. Search for **"Sealed Secrets"**
3. Click **Install** and restart Headlamp when prompted
#### Option 2: Build from Source
```bash
git clone https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin
cd headlamp-sealed-secrets-plugin
npm install
npm run build
```
Then copy the `dist` folder to your Headlamp plugins directory:
- **Linux**: `~/.config/Headlamp/plugins/headlamp-sealed-secrets/`
- **macOS**: `~/Library/Application Support/Headlamp/plugins/headlamp-sealed-secrets/`
- **Windows**: `%APPDATA%\Headlamp\plugins\headlamp-sealed-secrets\`
The plugin is sourced directly from [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp/headlamp-sealed-secrets).
## Usage
@@ -70,6 +58,15 @@ install: |
- Configure controller settings
For detailed usage instructions, see the [README](https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin/blob/main/README.md).
changes:
- kind: fixed
description: "Fix ArtifactHub checksum — release workflow now computes checksums after rebuilding tarball"
- kind: changed
description: "Bump to v1.0.0 — stable public release with comprehensive tests, ArtifactHub-only installation, and full RBAC-aware UI"
- kind: added
description: Explicit vitest and @testing-library devDependencies for reliable test execution
- kind: fixed
description: Removed install-plugin.sh custom install script (ArtifactHub-only policy)
maintainers:
- name: privilegedescalation
email: privilegedescalation@users.noreply.github.com
+2 -2
View File
@@ -121,7 +121,7 @@ For Headlamp running in Kubernetes:
kubectl create configmap headlamp-sealed-secrets-plugin \
--from-file=main.js=dist/main.js \
--from-file=package.json=package.json \
-n headlamp
-n kube-system
```
2. **Update Headlamp deployment**:
@@ -149,7 +149,7 @@ For Headlamp running in Kubernetes:
3. **Apply and restart**:
```bash
kubectl apply -f headlamp-deployment.yaml
kubectl rollout restart deployment/headlamp -n headlamp
kubectl rollout restart deployment/headlamp -n kube-system
```
## Verification
View File
+81
View File
@@ -0,0 +1,81 @@
import { test as setup, expect, Page } from '@playwright/test';
const AUTH_STATE_PATH = 'e2e/.auth/state.json';
async function authenticateWithOIDC(page: Page, username: string, password: string): Promise<void> {
// Navigate to login — Headlamp redirects / to /c/main/login
await page.goto('/');
await page.waitForURL('**/login');
// Click "Sign In" and capture the Authentik popup
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: /sign in/i }).click();
const popup = await popupPromise;
// Wait for the Authentik popup to fully load before interacting
await popup.waitForLoadState('domcontentloaded');
await popup.waitForLoadState('networkidle');
// Authentik step 1: fill username — wait for the form to render
const usernameField = popup.getByRole('textbox', { name: /email or username/i });
await usernameField.waitFor({ state: 'visible', timeout: 15_000 });
await usernameField.fill(username);
await popup.getByRole('button', { name: /log in/i }).click();
// Authentik step 2: fill password — wait for the next step to load
await popup.waitForLoadState('networkidle');
const passwordField = popup.getByRole('textbox', { name: /password/i });
await passwordField.waitFor({ state: 'visible', timeout: 15_000 });
await passwordField.fill(password);
await popup.getByRole('button', { name: /continue|log in/i }).click();
// Wait for the popup to close (Authentik redirects back, Headlamp processes callback)
await popup.waitForEvent('close', { timeout: 15_000 });
// Original page should now be authenticated — wait for sidebar
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({
timeout: 15_000,
});
}
async function authenticateWithToken(page: Page, token: string): Promise<void> {
await page.goto('/');
// Headlamp goes to /token directly when no OIDC is configured,
// or through /login when OIDC is configured
await page.waitForURL(/\/(login|token)$/);
if (page.url().includes('/login')) {
// OIDC login page — click "use a token" to reach token auth.
const useTokenBtn = page.getByRole('button', { name: /use a token/i });
await useTokenBtn.waitFor({ state: 'visible', timeout: 15_000 });
await useTokenBtn.click();
await page.waitForURL('**/token');
}
// Fill the "ID token" field and submit
await page.getByRole('textbox', { name: /id token/i }).fill(token);
await page.getByRole('button', { name: /authenticate/i }).click();
// Wait for the main UI to load
await expect(page.getByRole('navigation', { name: 'Navigation' })).toBeVisible({
timeout: 15_000,
});
}
setup('authenticate with Headlamp', async ({ page }) => {
const username = process.env.AUTHENTIK_USERNAME;
const password = process.env.AUTHENTIK_PASSWORD;
const token = process.env.HEADLAMP_TOKEN;
if (username && password) {
await authenticateWithOIDC(page, username, password);
} else if (token) {
await authenticateWithToken(page, token);
} else {
throw new Error(
'Set AUTHENTIK_USERNAME + AUTHENTIK_PASSWORD for OIDC auth, or HEADLAMP_TOKEN for token auth'
);
}
await page.context().storageState({ path: AUTH_STATE_PATH });
});
+88
View File
@@ -0,0 +1,88 @@
import { test, expect } from '@playwright/test';
test.describe('Sealed Secrets plugin smoke tests', () => {
test('sidebar contains sealed-secrets entry', async ({ page }) => {
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
await expect(sidebar.getByRole('button', { name: /sealed.secrets/i })).toBeVisible();
});
test('sidebar sealed-secrets entry is clickable and navigates to list view', async ({ page }) => {
await page.goto('/');
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
await expect(sidebar).toBeVisible({ timeout: 15_000 });
const sealedSecretsEntry = sidebar.getByRole('button', { name: /sealed.secrets/i });
await expect(sealedSecretsEntry).toBeVisible();
await sealedSecretsEntry.click();
await expect(page).toHaveURL(/\/sealedsecrets/);
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible();
});
test('sealed secrets list page renders table or empty state', async ({ page }) => {
await page.goto('/c/main/sealedsecrets');
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({
timeout: 15_000,
});
// Either a populated table or an empty-state indicator must be visible
const hasTable = await page.locator('table').first().isVisible().catch(() => false);
const hasEmptyState = await page
.locator('text=/no.*sealed|no.*secret|0 item|empty/i')
.first()
.isVisible()
.catch(() => false);
expect(hasTable || hasEmptyState).toBe(true);
});
test('sealing keys page renders table or empty state', async ({ page }) => {
await page.goto('/c/main/sealedsecrets/keys');
await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible({
timeout: 15_000,
});
const hasTable = await page.locator('table').first().isVisible().catch(() => false);
const hasEmptyState = await page
.locator('text=/no.*key|0 item|empty/i')
.first()
.isVisible()
.catch(() => false);
expect(hasTable || hasEmptyState).toBe(true);
});
test('navigation between sealed-secrets views works', async ({ page }) => {
await page.goto('/c/main/sealedsecrets');
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible({
timeout: 15_000,
});
// Navigate to Sealing Keys via sidebar
const sidebar = page.getByRole('navigation', { name: 'Navigation' });
const keysLink = sidebar.getByRole('link', { name: /sealing.key/i });
await expect(keysLink).toBeVisible();
await keysLink.click();
await expect(page).toHaveURL(/\/sealedsecrets\/keys$/);
await expect(page.getByRole('heading', { name: /sealing.key/i })).toBeVisible();
// Navigate back to All Sealed Secrets
const allSecretsLink = sidebar.getByRole('link', { name: /all sealed secrets/i });
await expect(allSecretsLink).toBeVisible();
await allSecretsLink.click();
await expect(page).toHaveURL(/\/sealedsecrets(?!\/keys)/);
await expect(page.getByRole('heading', { name: /sealed.secrets/i })).toBeVisible();
});
test('plugin settings page shows sealed-secrets plugin entry', async ({ page }) => {
await page.goto('/settings/plugins');
// Wait for plugin list to load — plugin scripts load asynchronously
const pluginEntry = page.locator('text=sealed-secrets').first();
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
});
});
-79
View File
@@ -1,79 +0,0 @@
#!/bin/bash
#
# Install Headlamp Sealed Secrets Plugin
#
# This script builds and installs the plugin to your local Headlamp installation.
#
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}Headlamp Sealed Secrets Plugin Installer${NC}"
echo "=========================================="
echo
# Detect OS and set plugin directory
if [[ "$OSTYPE" == "darwin"* ]]; then
PLUGIN_DIR="$HOME/Library/Application Support/Headlamp/plugins/headlamp-sealed-secrets"
echo -e "${YELLOW}Detected: macOS${NC}"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
PLUGIN_DIR="$HOME/.config/Headlamp/plugins/headlamp-sealed-secrets"
echo -e "${YELLOW}Detected: Linux${NC}"
else
echo -e "${RED}Unsupported OS: $OSTYPE${NC}"
echo "For Windows, please see HEADLAMP_INSTALLATION.md"
exit 1
fi
echo "Plugin will be installed to: $PLUGIN_DIR"
echo
# Check if node/npm are available
if ! command -v npm &> /dev/null; then
echo -e "${RED}Error: npm is not installed${NC}"
echo "Please install Node.js and npm first"
exit 1
fi
# Navigate to plugin directory
cd "$(dirname "$0")"
echo -e "${GREEN}Step 1: Installing dependencies...${NC}"
npm install
echo
echo -e "${GREEN}Step 2: Building plugin...${NC}"
npm run build
echo
echo -e "${GREEN}Step 3: Creating plugin directory...${NC}"
mkdir -p "$PLUGIN_DIR"
echo
echo -e "${GREEN}Step 4: Copying plugin files...${NC}"
cp -v dist/main.js "$PLUGIN_DIR/"
cp -v package.json "$PLUGIN_DIR/"
cp -v README.md "$PLUGIN_DIR/" 2>/dev/null || true
cp -v LICENSE "$PLUGIN_DIR/" 2>/dev/null || true
echo
echo -e "${GREEN}✓ Installation complete!${NC}"
echo
echo "Plugin installed to: $PLUGIN_DIR"
echo
echo "Next steps:"
echo "1. Restart Headlamp desktop application"
echo "2. Open Headlamp and connect to your cluster"
echo "3. Look for 'Sealed Secrets' in the sidebar"
echo
echo "To verify sealed-secrets controller is installed:"
echo " kubectl get pods -n kube-system -l name=sealed-secrets-controller"
echo
echo "To install sealed-secrets controller (if not present):"
echo " kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml"
echo
-18279
View File
File diff suppressed because it is too large Load Diff
+26 -5
View File
@@ -1,6 +1,6 @@
{
"name": "sealed-secrets",
"version": "0.2.24",
"version": "1.0.2",
"description": "Headlamp plugin for Bitnami Sealed Secrets - manage encrypted Kubernetes secrets",
"files": [
"dist",
@@ -17,6 +17,7 @@
"homepage": "https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin#readme",
"author": "privilegedescalation",
"license": "Apache-2.0",
"packageManager": "pnpm@10.32.1",
"scripts": {
"start": "headlamp-plugin start",
"build": "headlamp-plugin build",
@@ -28,6 +29,8 @@
"format:check": "prettier --check src/",
"test": "vitest run",
"test:watch": "vitest",
"e2e": "playwright test",
"e2e:headed": "playwright test --headed",
"storybook": "headlamp-plugin storybook",
"storybook-build": "headlamp-plugin storybook-build",
"i18n": "headlamp-plugin i18n",
@@ -47,18 +50,36 @@
"k8s"
],
"overrides": {
"typescript": "5.6.2",
"tar": "^7.5.11",
"undici": "^7.24.3"
"undici": "^7.24.3",
"vite": ">=6.4.2",
"lodash": ">=4.18.0"
},
"dependencies": {
"node-forge": "^1.3.1"
"node-forge": "^1.4.0"
},
"devDependencies": {
"@headlamp-k8s/eslint-config": "^0.6.0",
"@playwright/test": "^1.58.2",
"@iconify/react": "^6.0.2",
"@kinvolk/headlamp-plugin": "^0.13.0",
"@mui/material": "^5.15.14",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "^14.5.2",
"@types/node-forge": "^1.3.11",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"eslint": "^8.57.0",
"jsdom": "^24.0.0",
"notistack": "^3.0.0",
"prettier": "^2.8.8",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^5.3.0",
"typedoc": "^0.28.16",
"typedoc-plugin-markdown": "^4.10.0"
"typescript": "~5.6.2",
"typedoc-plugin-markdown": "^4.10.0",
"vitest": "^3.2.4"
}
}
+27
View File
@@ -0,0 +1,27 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30_000,
expect: { timeout: 10_000 },
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
reporter: 'list',
use: {
baseURL: process.env.HEADLAMP_URL || (() => { throw new Error('HEADLAMP_URL is required — run scripts/deploy-e2e-headlamp.sh first'); })(),
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'setup', testMatch: /auth\.setup\.ts/, timeout: 60_000 },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'e2e/.auth/state.json',
},
dependencies: ['setup'],
},
],
});
+12192
View File
File diff suppressed because it is too large Load Diff
+2 -16
View File
@@ -1,19 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"baseBranches": ["main"],
"schedule": ["every weekend"],
"prConcurrentLimit": 10,
"packageRules": [
{
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "npm minor and patch"
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions minor and patch"
}
]
"extends": ["github>privilegedescalation/.github:renovate-config"]
}
+204
View File
@@ -0,0 +1,204 @@
#!/usr/bin/env bash
# deploy-e2e-headlamp.sh
#
# Deploys a stock Headlamp instance with the sealed-secrets plugin loaded via
# a ConfigMap volume mount. No custom Docker images — the plugin is built
# in CI and injected as a ConfigMap.
#
# E2E resources are deployed to the `privilegedescalation-dev` namespace. Nothing
# persists beyond the test run — teardown cleans up all created resources.
#
# Prerequisites:
# - Plugin built (dist/ exists with plugin-main.js + package.json)
# - kubectl configured with cluster access
# - RBAC applied: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
#
# Environment:
# E2E_NAMESPACE — namespace for E2E Headlamp (default: privilegedescalation-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
# HEADLAMP_VERSION — Headlamp image tag (default: latest)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DIST_DIR="$REPO_ROOT/dist"
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
HEADLAMP_VERSION="${HEADLAMP_VERSION:-latest}"
if [ ! -d "$DIST_DIR" ]; then
echo "ERROR: dist/ not found. Run 'pnpm build' first." >&2
exit 1
fi
# --- Preflight: verify RBAC before touching the cluster ---
echo "Checking RBAC permissions in namespace '${E2E_NAMESPACE}'..."
if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/null; then
echo "ERROR: Missing RBAC — cannot delete configmaps in namespace '${E2E_NAMESPACE}'." >&2
echo " Apply RBAC first: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml" >&2
exit 1
fi
echo "=== E2E Headlamp Deployment ==="
echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}"
echo " Namespace: $E2E_NAMESPACE"
echo " Release: $E2E_RELEASE"
# --- Create ConfigMap from built plugin ---
echo ""
echo "Creating ConfigMap with plugin files..."
# Delete existing ConfigMap if present (idempotent redeploy)
kubectl delete configmap headlamp-sealed-secrets-plugin \
-n "$E2E_NAMESPACE" --ignore-not-found
# Create ConfigMap from dist/ contents and package.json
kubectl create configmap headlamp-sealed-secrets-plugin \
-n "$E2E_NAMESPACE" \
--from-file="$DIST_DIR" \
--from-file=package.json="$REPO_ROOT/package.json"
# --- Tear down any existing E2E deployment for a clean start ---
echo ""
echo "Removing any existing E2E deployment (clean-start)..."
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
# --- Deploy Headlamp via kubectl apply ---
echo ""
echo "Deploying Headlamp E2E instance..."
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
template:
metadata:
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
serviceAccountName: ${E2E_RELEASE}
automountServiceAccountToken: true
securityContext: {}
containers:
- name: headlamp
image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}
imagePullPolicy: IfNotPresent
securityContext:
runAsNonRoot: true
privileged: false
runAsUser: 100
runAsGroup: 101
args:
- "-in-cluster"
- "-in-cluster-context-name=main"
- "-plugins-dir=/headlamp/plugins"
ports:
- name: http
containerPort: 4466
protocol: TCP
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 10
volumeMounts:
- name: sealed-secrets-plugin
mountPath: /headlamp/plugins/headlamp-sealed-secrets
readOnly: true
volumes:
- name: sealed-secrets-plugin
configMap:
name: headlamp-sealed-secrets-plugin
---
apiVersion: v1
kind: Service
metadata:
name: ${E2E_RELEASE}
namespace: ${E2E_NAMESPACE}
labels:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: headlamp
app.kubernetes.io/instance: ${E2E_RELEASE}
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
EOF
echo "Waiting for rollout..."
kubectl rollout status "deployment/${E2E_RELEASE}" \
-n "$E2E_NAMESPACE" --timeout=120s
# --- Generate a service URL for tests ---
SVC_URL="http://${E2E_RELEASE}.${E2E_NAMESPACE}.svc.cluster.local"
# --- Wait for DNS and HTTP reachability ---
echo ""
echo "Waiting for ${SVC_URL} to be reachable..."
ATTEMPTS=0
MAX_ATTEMPTS=24 # 24 × 5s = 120s max
until curl -sf --max-time 5 "${SVC_URL}" -o /dev/null 2>/dev/null; do
ATTEMPTS=$((ATTEMPTS + 1))
if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then
echo "ERROR: ${SVC_URL} not reachable after $((MAX_ATTEMPTS * 5))s" >&2
exit 1
fi
echo " [${ATTEMPTS}/${MAX_ATTEMPTS}] not yet reachable, retrying in 5s..."
sleep 5
done
echo ""
echo "E2E Headlamp is ready at: ${SVC_URL}"
echo " export HEADLAMP_URL=${SVC_URL}"
# --- Generate a token for test auth ---
echo ""
echo "Creating service account token for E2E auth..."
kubectl create serviceaccount headlamp-e2e-test \
-n "$E2E_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "")
if [ -n "$TOKEN" ]; then
echo " export HEADLAMP_TOKEN=<generated>"
echo ""
echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e"
echo "HEADLAMP_TOKEN=${TOKEN}" >> "$REPO_ROOT/.env.e2e"
echo "Wrote .env.e2e with HEADLAMP_URL and HEADLAMP_TOKEN"
else
echo " WARNING: Could not generate token. Set HEADLAMP_TOKEN manually or use OIDC."
fi
echo ""
echo "E2E deployment complete."
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# teardown-e2e-headlamp.sh
#
# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh.
#
# Environment:
# E2E_NAMESPACE — namespace to clean up (default: privilegedescalation-dev)
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
echo "=== E2E Headlamp Teardown ==="
echo " Namespace: $E2E_NAMESPACE"
echo " Release: $E2E_RELEASE"
echo "Removing Headlamp Deployment, Service, and ServiceAccount..."
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up ConfigMap..."
kubectl delete configmap headlamp-sealed-secrets-plugin -n "$E2E_NAMESPACE" --ignore-not-found
echo "Cleaning up test service account..."
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found
# Clean up .env.e2e if present
if [ -f "$REPO_ROOT/.env.e2e" ]; then
rm "$REPO_ROOT/.env.e2e"
echo "Removed .env.e2e"
fi
echo ""
echo "E2E teardown complete."
+1 -1
View File
@@ -1,7 +1,7 @@
{
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
"compilerOptions": {
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "@testing-library/jest-dom"]
"types": ["vitest/globals", "@testing-library/jest-dom"]
},
"include": ["src"]
}
+3
View File
@@ -1,6 +1,9 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
define: {
'process.env.NODE_ENV': '"test"',
},
test: {
globals: true,
environment: 'jsdom',