From 1bf3087bad699f82d2fcb7806f5ad80b42f3d450 Mon Sep 17 00:00:00 2001 From: Makito Date: Tue, 18 Feb 2025 10:48:10 +0800 Subject: [PATCH] refactor(*): make build result CSP compliant --- .ci/docker-compose.yml | 1 + .../actions/select-gateway-image/action.yml | 2 +- .github/workflows/.reusable_e2e_tests.yml | 13 +++++++- .github/workflows/test.yml | 12 ++++++++ package.json | 3 +- pnpm-lock.yaml | 30 +++---------------- src/main.ts | 3 ++ src/monaco-workers.ts | 9 ++++++ tests/playwright/base-test.ts | 9 ++++++ tests/playwright/env.d.ts | 16 ++++++++++ tests/playwright/helpers/console.ts | 15 ++++++++++ tests/playwright/playwright.config.ts | 5 +++- tsconfig.json | 5 ++-- vite.config.ts | 6 ---- 14 files changed, 90 insertions(+), 39 deletions(-) create mode 100644 src/monaco-workers.ts create mode 100644 tests/playwright/env.d.ts diff --git a/.ci/docker-compose.yml b/.ci/docker-compose.yml index 792cfe84..168f6bfb 100644 --- a/.ci/docker-compose.yml +++ b/.ci/docker-compose.yml @@ -51,6 +51,7 @@ services: KONG_ADMIN_LISTEN: "0.0.0.0:8001" KONG_ANONYMOUS_REPORTS: "off" KONG_ROUTER_FLAVOR: + KONG_ADMIN_GUI_CSP_HEADER: restart: always depends_on: &kong-depends_on diff --git a/.github/actions/select-gateway-image/action.yml b/.github/actions/select-gateway-image/action.yml index 506ef3e5..cfe8b874 100644 --- a/.github/actions/select-gateway-image/action.yml +++ b/.github/actions/select-gateway-image/action.yml @@ -16,7 +16,7 @@ runs: id: select-image shell: bash env: - DEFAULT_GATEWAY_IMAGE: kong/kong-dev:master-ubuntu + DEFAULT_GATEWAY_IMAGE: kong/kong-dev:pr-14287-ubuntu # kong/kong-dev:master-ubuntu run: | GATEWAY_IMAGE="${{ inputs.current-image }}" diff --git a/.github/workflows/.reusable_e2e_tests.yml b/.github/workflows/.reusable_e2e_tests.yml index 7aabfd02..9aa0ab82 100644 --- a/.github/workflows/.reusable_e2e_tests.yml +++ b/.github/workflows/.reusable_e2e_tests.yml @@ -16,6 +16,9 @@ on: load-test-image-from-artifact: type: boolean default: false + enable-csp-header: + type: boolean + default: false jobs: e2e-tests: @@ -78,7 +81,7 @@ jobs: uses: actions/download-artifact@v4 with: name: docker-test-image - + - name: Load test image if: ${{ inputs.load-test-image-from-artifact }} run: | @@ -98,6 +101,7 @@ jobs: env: GATEWAY_IMAGE: ${{ inputs.gateway-image }} KONG_ROUTER_FLAVOR: ${{ matrix.router-flavor }} + KONG_ADMIN_GUI_CSP_HEADER: ${{ inputs.enable-csp-header && 'on' || 'off' }} run: | _compose_exit=0 docker compose -f .ci/docker-compose.yml up -d kong --wait || _compose_exit=$? @@ -119,6 +123,13 @@ jobs: docker compose -f .ci/docker-compose.yml logs exit $_compose_exit + - name: Ensure CSP header + if: ${{ inputs.enable-csp-header }} + run: | + header_lines=$(curl -s -D - -o /dev/null http://localhost:8002/index.html) + echo $header_lines + echo $header_lines | grep -q "Content-Security-Policy: default-src 'self';" + - name: Run E2E tests timeout-minutes: 10 env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 160b292d..b4b3bbcb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,12 +23,24 @@ jobs: uses: ./.github/workflows/.reusable_test_image.yml needs: build secrets: inherit + with: + gateway-image: docker.io/kong/kong-dev:pr-14287-ubuntu # TODO: Revert this line before merging e2e-tests: name: E2E Tests needs: build-test-image uses: ./.github/workflows/.reusable_e2e_tests.yml + secrets: inherit with: gateway-image: ${{ needs.build-test-image.outputs.image }} load-test-image-from-artifact: true + + e2e-tests-csp-on: + name: E2E Tests (CSP on) + needs: build-test-image + uses: ./.github/workflows/.reusable_e2e_tests.yml secrets: inherit + with: + gateway-image: ${{ needs.build-test-image.outputs.image }} + load-test-image-from-artifact: true + enable-csp-header: true diff --git a/package.json b/package.json index 4e000ad8..35ddb536 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@material-design-icons/font": "^0.14.13", "axios": "^1.7.7", "dayjs": "^1.11.13", - "monaco-editor": "0.52.0", + "monaco-editor": "0.52.2", "pinia": "^3.0.1", "vue": "^3.5.12", "vue-router": "^4.4.5" @@ -76,7 +76,6 @@ "typescript": "^5.4.5", "vite": "^6.1.1", "vite-plugin-html": "^3.2.2", - "vite-plugin-monaco-editor": "^1.1.0", "vue-tsc": "^2.1.4" }, "browserslist": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a789b39..4db2109a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,8 +75,8 @@ importers: specifier: ^1.11.13 version: 1.11.13 monaco-editor: - specifier: 0.52.0 - version: 0.52.0 + specifier: 0.52.2 + version: 0.52.2 pinia: specifier: ^3.0.1 version: 3.0.1(typescript@5.4.5)(vue@3.5.12(typescript@5.4.5)) @@ -162,9 +162,6 @@ importers: vite-plugin-html: specifier: ^3.2.2 version: 3.2.2(vite@6.1.1(@types/node@20.4.2)(sass@1.80.3)(terser@5.18.2)(yaml@2.5.0)) - vite-plugin-monaco-editor: - specifier: ^1.1.0 - version: 1.1.0(monaco-editor@0.52.0) vue-tsc: specifier: ^2.1.4 version: 2.1.4(typescript@5.4.5) @@ -504,9 +501,6 @@ packages: '@jridgewell/sourcemap-codec@1.4.14': resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -2089,9 +2083,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - monaco-editor@0.52.0: - resolution: {integrity: sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==} - monaco-editor@0.52.2: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} @@ -2655,11 +2646,6 @@ packages: peerDependencies: vite: '>=2.0.0' - vite-plugin-monaco-editor@1.1.0: - resolution: {integrity: sha512-IvtUqZotrRoVqwT0PBBDIZPNraya3BxN/bfcNfnxZ5rkJiGcNtO5eAOWWSgT7zullIAEqQwxMU83yL9J5k7gww==} - peerDependencies: - monaco-editor: '>=0.33.0' - vite@6.1.1: resolution: {integrity: sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3059,7 +3045,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.3': dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.18 '@jridgewell/resolve-uri@3.1.0': {} @@ -3073,8 +3059,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.14': {} - '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.18': @@ -4764,8 +4748,6 @@ snapshots: mitt@3.0.1: {} - monaco-editor@0.52.0: {} - monaco-editor@0.52.2: {} ms@2.1.2: {} @@ -5310,17 +5292,13 @@ snapshots: dotenv: 16.4.5 dotenv-expand: 8.0.3 ejs: 3.1.10 - fast-glob: 3.3.2 + fast-glob: 3.3.3 fs-extra: 10.1.0 html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 vite: 6.1.1(@types/node@20.4.2)(sass@1.80.3)(terser@5.18.2)(yaml@2.5.0) - vite-plugin-monaco-editor@1.1.0(monaco-editor@0.52.0): - dependencies: - monaco-editor: 0.52.0 - vite@6.1.1(@types/node@20.4.2)(sass@1.80.3)(terser@5.18.2)(yaml@2.5.0): dependencies: esbuild: 0.24.2 diff --git a/src/main.ts b/src/main.ts index 18ed42ac..f791f5e6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,9 @@ import { registerGlobalComponents } from './registerGlobalComponents' import './styles/index' import { createPinia } from 'pinia' +// This only sets up worker initializers. They will be lazy-loaded when needed. +import '@/monaco-workers' + const i18n = createI18n('en-us', english, { isGlobal: true }) const app = createApp(App) diff --git a/src/monaco-workers.ts b/src/monaco-workers.ts new file mode 100644 index 00000000..d7a7b97a --- /dev/null +++ b/src/monaco-workers.ts @@ -0,0 +1,9 @@ +// Injecting workers for monaco-editor + +import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' + +self.MonacoEnvironment = { + getWorker() { + return new EditorWorker() + }, +} diff --git a/tests/playwright/base-test.ts b/tests/playwright/base-test.ts index 4de803ed..05b34698 100644 --- a/tests/playwright/base-test.ts +++ b/tests/playwright/base-test.ts @@ -1,6 +1,7 @@ import { test as pwTest, type BrowserContext, type Page } from '@playwright/test' import config from '@pw-config' import { withNavigation } from './commands/withNavigation' +import { failOnCSPViolations } from './helpers/console' export interface SharedState { context?: BrowserContext | null @@ -69,7 +70,15 @@ const baseTest = (sharedState: SharedState | null = {}) => { // make playwright always run tests from first to last when retry happens t.describe.configure({ mode: 'serial' }) + let unregisterCSPViolationWatcher: () => void | undefined + t.beforeAll(async ({ page })=>{ + if (config.use.failOnCSPViolations) { + unregisterCSPViolationWatcher = failOnCSPViolations(page) + } + }) + t.afterAll(async ({ context, page }) => { + unregisterCSPViolationWatcher?.() await page.close() await context.close() }) diff --git a/tests/playwright/env.d.ts b/tests/playwright/env.d.ts new file mode 100644 index 00000000..ce48cce1 --- /dev/null +++ b/tests/playwright/env.d.ts @@ -0,0 +1,16 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + KM_TEST_GUI_URL?: string + // We have fallback for this env variable in .env + KM_TEST_API_URL: string + + /** + * String-presented boolean + */ + KM_TEST_FAIL_ON_CSP_VIOLATIONS?: string + } + } +} + +export {} \ No newline at end of file diff --git a/tests/playwright/helpers/console.ts b/tests/playwright/helpers/console.ts index 5fe9922a..c68f5d45 100644 --- a/tests/playwright/helpers/console.ts +++ b/tests/playwright/helpers/console.ts @@ -14,3 +14,18 @@ export const observeConsole = (page: Page) => { return [messages, teardown] as const } + +export const failOnCSPViolations = (page: Page): () => void => { + const message = (message: ConsoleMessage) => { + if ((message.type() === 'error' || message.type() === 'warning') && /violates.*Content Security Policy/.test(message.text())) { + throw new Error(message.text()) + } + } + console.log('[Console Helper] Registered CSP violation watcher. Tests will fail if a CSP violation is detected.') + page.on('console', message) + + return () => { + page.off('console', message) + console.log('[Console Helper] CSP violation watcher unregistered.') + } +} \ No newline at end of file diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts index 5ece3c51..87f8383e 100644 --- a/tests/playwright/playwright.config.ts +++ b/tests/playwright/playwright.config.ts @@ -3,7 +3,9 @@ import * as dotenv from 'dotenv' dotenv.config({ path: '../../.env' }) -const config: PlaywrightTestConfig = { +const config: PlaywrightTestConfig<{ + failOnCSPViolations: boolean +}> = { reporter: [['junit', { outputFile: './test-results/playwright.xml' }]], testDir: '.', // Using default value here. Let's keep it here to prevent it from being forgotten in future migrations. retries: 1, @@ -20,6 +22,7 @@ const config: PlaywrightTestConfig = { contextOptions: { permissions: ['clipboard-read', 'clipboard-write'], }, + failOnCSPViolations: /^1|true$/i.test(process.env.KM_TEST_FAIL_ON_CSP_VIOLATIONS ?? ''), }, } diff --git a/tsconfig.json b/tsconfig.json index 319ba578..93763995 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,12 +9,13 @@ "tests/*": ["tests/*"], "config": ["src/config.ts"] }, - "moduleResolution": "node", + "moduleResolution": "bundler", "resolveJsonModule": true, "allowJs": true, "noImplicitAny": false, "types": [ - "vite/client" + "vite/client", + "monaco-editor", ] }, "exclude": ["node_modules", "dist", "coverage", "tests"] diff --git a/vite.config.ts b/vite.config.ts index 222f87db..a64c4e94 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,7 +4,6 @@ import path from 'path' import autoprefixer from 'autoprefixer' import { createHtmlPlugin } from 'vite-plugin-html' import { visualizer } from 'rollup-plugin-visualizer' -import monacoEditorPlugin from 'vite-plugin-monaco-editor' const basePath = process.env.NODE_ENV !== 'production' || process.env.DISABLE_BASE_PATH === 'true' ? '/' : '/__km_base__/' @@ -33,11 +32,6 @@ export default defineConfig({ }, }, }), - // See: https://github.com/vdesjs/vite-plugin-monaco-editor/issues/21 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ((monacoEditorPlugin as any).default as typeof monacoEditorPlugin)({ - languageWorkers: ['editorWorkerService'], - }), visualizer({ filename: path.resolve(__dirname, 'bundle-analyzer/stats-treemap.html'), template: 'treemap', // sunburst|treemap|network