From 5e8f0c6f52d0ac0b2dd535a6fe099d550b99cfa3 Mon Sep 17 00:00:00 2001 From: Jack Westbrook Date: Wed, 11 Dec 2024 15:24:31 +0100 Subject: [PATCH] feat(trustedtypes): update vite config and the webassets integration to work with TT --- pkg/api/webassets/webassets.go | 21 ++++++++++++++++++--- public/app/core/trustedTypePolicies.ts | 9 ++++----- public/app/index.ts | 1 - scripts/grafana-server/custom.ini | 2 +- vite.config.ts | 16 +++++++++++++++- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/pkg/api/webassets/webassets.go b/pkg/api/webassets/webassets.go index 8ebf45ce538..bfc05588d1e 100644 --- a/pkg/api/webassets/webassets.go +++ b/pkg/api/webassets/webassets.go @@ -34,6 +34,7 @@ type ViteManifestEntry struct { IsDynamicEntry bool `json:"isDynamicEntry"` IsEntry bool `json:"isEntry"` Src string `json:"src"` + Name string `json:"name"` } type Manifest map[string]ViteManifestEntry @@ -142,8 +143,10 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { return nil, fmt.Errorf("failed to read assets-manifest.json %w", err) } - // TODO: This is a temporary hack to get the entrypoints for the vite frontend loading. - var entryPointJSAssets []dtos.EntryPointAsset + // TODO: This is temporary to get the entrypoints for the vite frontend loading. + // We need trusted types up front otherwise the entire app will fail to load if they're enabled. + var trustedTypePolicyAsset *dtos.EntryPointAsset + var otherJSAssets []dtos.EntryPointAsset var preloadJSAssets []dtos.EntryPointAsset var darkCSS, lightCSS string @@ -155,7 +158,11 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { Integrity: "", } if strings.HasSuffix(entry.File, ".js") { - entryPointJSAssets = append(entryPointJSAssets, asset) + if entry.Name == "trustedTypePolicies" { + trustedTypePolicyAsset = &asset + } else { + otherJSAssets = append(otherJSAssets, asset) + } preloadJSAssets = getPreloadChunks(manifest, entry.Src) } if entry.Src == "sass/grafana.dark.scss" && entry.IsEntry { @@ -192,6 +199,14 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { // return nil, fmt.Errorf("missing swagger entry, try running `yarn build`") // } + var entryPointJSAssets []dtos.EntryPointAsset + + entryPointJSAssets = append([]dtos.EntryPointAsset{*trustedTypePolicyAsset}, otherJSAssets...) + + if entryPointJSAssets == nil { + return nil, fmt.Errorf("no entrypoints found in assets manifest, try running `yarn build`") + } + rsp := &dtos.EntryPointAssets{ JSFiles: entryPointJSAssets, PreloadJSFiles: preloadJSAssets, diff --git a/public/app/core/trustedTypePolicies.ts b/public/app/core/trustedTypePolicies.ts index 6dedb7f68e8..76144521c4f 100644 --- a/public/app/core/trustedTypePolicies.ts +++ b/public/app/core/trustedTypePolicies.ts @@ -1,7 +1,6 @@ -import { textUtil } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { sanitizeUrl } from '@braintree/sanitize-url'; -const CSP_REPORT_ONLY_ENABLED = config.bootData.settings.cspReportOnlyEnabled; +const CSP_REPORT_ONLY_ENABLED = window.grafanaBootData!.settings.cspReportOnlyEnabled; export const defaultTrustedTypesPolicy = { createHTML: (string: string, source: string, sink: string) => { @@ -14,7 +13,7 @@ export const defaultTrustedTypesPolicy = { createScript: (string: string) => string, createScriptURL: (string: string, source: string, sink: string) => { if (!CSP_REPORT_ONLY_ENABLED) { - return textUtil.sanitizeUrl(string); + return sanitizeUrl(string); } console.error('[ScriptURL not sanitized with Trusted Types]', string, source, sink); return string; @@ -22,7 +21,7 @@ export const defaultTrustedTypesPolicy = { }; if ( - config.bootData.settings.trustedTypesDefaultPolicyEnabled && + window.grafanaBootData!.settings.trustedTypesDefaultPolicyEnabled && window.trustedTypes && window.trustedTypes.createPolicy ) { diff --git a/public/app/index.ts b/public/app/index.ts index 777cb314236..e16cee302fc 100644 --- a/public/app/index.ts +++ b/public/app/index.ts @@ -1,6 +1,5 @@ import './viteGlobals'; import 'vite/modulepreload-polyfill'; -import './core/trustedTypePolicies'; // TODO: Vite does this differently... // declare let __webpack_public_path__: string; diff --git a/scripts/grafana-server/custom.ini b/scripts/grafana-server/custom.ini index 69d48124d64..98a518778ec 100644 --- a/scripts/grafana-server/custom.ini +++ b/scripts/grafana-server/custom.ini @@ -1,7 +1,7 @@ [security] content_security_policy = true -content_security_policy_template = """script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" +content_security_policy_template = """require-trusted-types-for 'script'; script-src 'self' 'unsafe-eval' 'unsafe-inline' 'strict-dynamic' $NONCE;object-src 'none';font-src 'self';style-src 'self' 'unsafe-inline' blob:;img-src * data:;base-uri 'self';connect-src 'self' grafana.com ws://$ROOT_PATH wss://$ROOT_PATH;manifest-src 'self';media-src 'none';form-action 'self';""" enable_frontend_sandbox_for_plugins = sandbox-app-test,sandbox-test-datasource,sandbox-test-panel [feature_toggles] diff --git a/vite.config.ts b/vite.config.ts index 8b0f17c6408..af3c0bbafff 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -28,7 +28,21 @@ export default defineConfig(({ command }) => ({ // use manifest for backend integration in production manifest: 'public/build/.vite/manifest.json', rollupOptions: { - input: ['./public/app/index.ts', './public/sass/grafana.dark.scss', './public/sass/grafana.light.scss'], + input: [ + // trustedTypePolicies.ts is a special case because it needs to be loaded before the index.js and vendor.js + // otherwise the policy is not applied and grafana fails to load. + './public/app/core/trustedTypePolicies.ts', + './public/app/index.ts', + './public/sass/grafana.dark.scss', + './public/sass/grafana.light.scss', + ], + output: { + manualChunks(id) { + if (id.includes('@braintree/sanitize-url')) { + return 'braintree'; + } + }, + }, }, outDir: 'build_tmp', assetsDir: 'public/build',