diff --git a/cmd/provision.go b/cmd/provision.go index 31806f3..2744efa 100644 --- a/cmd/provision.go +++ b/cmd/provision.go @@ -13,9 +13,10 @@ import ( var Provision wisski_distillery.Command = pv{} type pv struct { - PHPVersion string `short:"p" long:"php" description:"specific php version to use for instance. Should be one of '8.0', '8.1'."` - OPCacheDevelopment bool `short:"o" long:"opcache-devel" description:"Include opcache development configuration"` - Positionals struct { + PHPVersion string `short:"p" long:"php" description:"specific php version to use for instance. Should be one of '8.0', '8.1'."` + OPCacheDevelopment bool `short:"o" long:"opcache-devel" description:"Include opcache development configuration"` + ContentSecurityPolicy string `short:"c" long:"content-security-policy" description:"Setup ContentSecurityPolicy"` + Positionals struct { Slug string `positional-arg-name:"slug" required:"1-1" description:"slug of instance to create"` } `positional-args:"true"` } @@ -41,8 +42,9 @@ func (p pv) Run(context wisski_distillery.Context) error { instance, err := context.Environment.Provision().Provision(context.Stderr, context.Context, provision.Flags{ Slug: p.Positionals.Slug, System: models.System{ - PHP: p.PHPVersion, - OpCacheDevelopment: p.OPCacheDevelopment, + PHP: p.PHPVersion, + OpCacheDevelopment: p.OPCacheDevelopment, + ContentSecurityPolicy: p.ContentSecurityPolicy, }, }) if err != nil { diff --git a/cmd/rebuild.go b/cmd/rebuild.go index f97ce72..9c4cf39 100644 --- a/cmd/rebuild.go +++ b/cmd/rebuild.go @@ -18,9 +18,11 @@ var Rebuild wisski_distillery.Command = rebuild{} type rebuild struct { Parallel int `short:"a" long:"parallel" description:"run on (at most) this many instances in parallel. 0 for no limit." default:"1"` - PHPVersion string `short:"p" long:"php" description:"update to specific php version to use for instance. Should be one of '8.0', '8.1'."` - OPCacheDevelopment bool `short:"o" long:"opcache-devel" description:"Include opcache development configuration"` - Positionals struct { + PHPVersion string `short:"p" long:"php" description:"update to specific php version to use for instance. Should be one of '8.0', '8.1'."` + OPCacheDevelopment bool `short:"o" long:"opcache-devel" description:"Include opcache development configuration"` + ContentSecurityPolicy string `short:"c" long:"content-security-policy" description:"Setup ContentSecurityPolicy"` + + Positionals struct { Slug []string `positional-arg-name:"SLUG" required:"0" description:"slug of instance or instances to run rebuild"` } `positional-args:"true"` } @@ -56,6 +58,7 @@ func (rb rebuild) Run(context wisski_distillery.Context) (err error) { return instance.SystemManager().Apply(context.Context, writer, models.System{ PHP: rb.PHPVersion, OpCacheDevelopment: rb.OPCacheDevelopment, + ContentSecurityPolicy: rb.ContentSecurityPolicy, }, true) }, wissKIs, status.SmartMessage(func(item *wisski.WissKI) string { return fmt.Sprintf("rebuild %q", item.Slug) diff --git a/internal/cli/cli_notices.go b/internal/cli/cli_notices.go index 082c426..03bef73 100755 --- a/internal/cli/cli_notices.go +++ b/internal/cli/cli_notices.go @@ -1,7 +1,7 @@ package cli // =========================================================================================================== -// This file was generated automatically at 13-07-2023 13:31:03 using gogenlicense. +// This file was generated automatically at 13-07-2023 21:49:29 using gogenlicense. // Do not edit manually, as changes may be overwritten. // =========================================================================================================== @@ -4913,7 +4913,7 @@ package cli // # Generation // // This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool. -// It was last updated at 13-07-2023 13:31:03. +// It was last updated at 13-07-2023 21:49:29. var LegalNotices string func init() { diff --git a/internal/dis/component/server/admin/html/instance.html b/internal/dis/component/server/admin/html/instance.html index e0028aa..68c03f3 100644 --- a/internal/dis/component/server/admin/html/instance.html +++ b/internal/dis/component/server/admin/html/instance.html @@ -120,6 +120,14 @@ {{ .Instance.System.OpCacheDevelopment }} + + + Content Security Policy + + + {{ .Instance.System.ContentSecurityPolicy }} + + @@ -143,7 +151,7 @@ Directory - {{ .Instance.FilesystemBase }} + {{ .Instance.FilesystemBase }} @@ -351,7 +359,7 @@ {{ $slug := .Instance.Slug }} {{ $csrf := .CSRF }} {{ range $index, $user := .Info.Users }} - + {{ $user.UID }} diff --git a/internal/dis/component/server/admin/html/instance_provision.html b/internal/dis/component/server/admin/html/instance_provision.html index 2d7ccb7..6ced76f 100644 --- a/internal/dis/component/server/admin/html/instance_provision.html +++ b/internal/dis/component/server/admin/html/instance_provision.html @@ -5,7 +5,7 @@
- +
@@ -24,6 +24,10 @@ Opache Development Configuration +
+ + +
Profile @@ -35,4 +39,9 @@ - \ No newline at end of file + + + {{ range .ContentSecurityPolicies }} + \ No newline at end of file diff --git a/internal/dis/component/server/admin/html/instance_rebuild.html b/internal/dis/component/server/admin/html/instance_rebuild.html index 1e9875f..f8ed7e7 100644 --- a/internal/dis/component/server/admin/html/instance_rebuild.html +++ b/internal/dis/component/server/admin/html/instance_rebuild.html @@ -1,11 +1,11 @@
-
+
Main Parameters
- +
@@ -22,9 +22,13 @@
+
+ + +
Profile @@ -36,4 +40,10 @@ - \ No newline at end of file + + + + {{ range .ContentSecurityPolicies }} + \ No newline at end of file diff --git a/internal/dis/component/server/admin/html/users.html b/internal/dis/component/server/admin/html/users.html index e51ca7b..91389fc 100644 --- a/internal/dis/component/server/admin/html/users.html +++ b/internal/dis/component/server/admin/html/users.html @@ -37,7 +37,7 @@ {{ $csrf := .CSRF }} {{ range .Users }} - + {{ .User.User }} diff --git a/internal/dis/component/server/admin/instance_provision.go b/internal/dis/component/server/admin/instance_provision.go index e6a8a1a..fd2aa31 100644 --- a/internal/dis/component/server/admin/instance_provision.go +++ b/internal/dis/component/server/admin/instance_provision.go @@ -27,12 +27,14 @@ type instanceProvisionContext struct { } type systemParams struct { - PHPVersions []string - DefaultPHPVersion string + PHPVersions []string + ContentSecurityPolicies []string + DefaultPHPVersion string } func newSystemParams() (sp systemParams) { sp.PHPVersions = models.KnownPHPVersions() + sp.ContentSecurityPolicies = models.ContentSecurityPolicyExamples() sp.DefaultPHPVersion = models.DefaultPHPVersion return sp } diff --git a/internal/dis/component/server/assets/assets_dist.go b/internal/dis/component/server/assets/assets_dist.go index 25e246a..0f462fd 100644 --- a/internal/dis/component/server/assets/assets_dist.go +++ b/internal/dis/component/server/assets/assets_dist.go @@ -25,17 +25,17 @@ var AssetsUser = Assets{ // AssetsAdmin contains assets for the 'Admin' entrypoint. var AssetsAdmin = Assets{ Scripts: ``, - Styles: ``, + Styles: ``, } // AssetsAdminProvision contains assets for the 'AdminProvision' entrypoint. var AssetsAdminProvision = Assets{ - Scripts: ``, - Styles: ``, + Scripts: ``, + Styles: ``, } // AssetsAdminRebuild contains assets for the 'AdminRebuild' entrypoint. var AssetsAdminRebuild = Assets{ - Scripts: ``, - Styles: ``, + Scripts: ``, + Styles: ``, } diff --git a/internal/dis/component/server/assets/dist/Admin.78d18bfa.css b/internal/dis/component/server/assets/dist/Admin.78d18bfa.css new file mode 100644 index 0000000..da028c0 --- /dev/null +++ b/internal/dis/component/server/assets/dist/Admin.78d18bfa.css @@ -0,0 +1 @@ +.wisski{padding:1em}.wisski h3{padding:0}.wisski a.pure-button{float:right;position:relative;bottom:1em}.wisski.running{background-color:#9ada07}.wisski.stopped{background-color:#ff7a7a}.disabled{color:gray}.overflow{overflow:auto}.info-chip{min-width:75px;height:15px;text-align:center;border-radius:5px;padding:5px;display:block}.info-chip.note{color:#fff;background-color:gray}.info-chip.info{color:#fff;background-color:green}.info-chip.warning{color:#000;background-color:#ff0}.info-chip.error{color:#000;background-color:red} \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/Admin.d79c7b30.css b/internal/dis/component/server/assets/dist/Admin.d79c7b30.css deleted file mode 100644 index b695168..0000000 --- a/internal/dis/component/server/assets/dist/Admin.d79c7b30.css +++ /dev/null @@ -1 +0,0 @@ -.wisski{padding:1em}.wisski h3{padding:0}.wisski a.pure-button{float:right;position:relative;bottom:1em}.wisski.running{background-color:#9ada07}.wisski.stopped{background-color:#ff7a7a}.info-chip{min-width:75px;height:15px;text-align:center;border-radius:5px;padding:5px;display:block} \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminProvision.68dbff79.js b/internal/dis/component/server/assets/dist/AdminProvision.0b361a8e.js similarity index 60% rename from internal/dis/component/server/assets/dist/AdminProvision.68dbff79.js rename to internal/dis/component/server/assets/dist/AdminProvision.0b361a8e.js index ba9502b..6a040fd 100644 --- a/internal/dis/component/server/assets/dist/AdminProvision.68dbff79.js +++ b/internal/dis/component/server/assets/dist/AdminProvision.0b361a8e.js @@ -1 +1 @@ -!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,i=o("8vh0V");async function l(e){return await new Promise(((n,t)=>{(0,i.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(null!=r?r:"unspecified error"))}})}))}const a=document.getElementById("provision"),d=document.getElementById("slug"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),l({Slug:d.value,System:{PHP:u.value,OpCacheDevelopment:c.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=a.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}(); \ No newline at end of file +!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,i=o("8vh0V");async function l(e){return await new Promise(((n,t)=>{(0,i.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("provision"),a=document.getElementById("slug"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment"),s=document.getElementById("contentsecuritypolicy");d.addEventListener("submit",(e=>{e.preventDefault(),l({Slug:a.value,System:{PHP:u.value,OpCacheDevelopment:c.checked,ContentSecurityPolicy:s.value}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=d.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}(); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminProvision.9cbda41c.js b/internal/dis/component/server/assets/dist/AdminProvision.9cbda41c.js deleted file mode 100644 index 99b4802..0000000 --- a/internal/dis/component/server/assets/dist/AdminProvision.9cbda41c.js +++ /dev/null @@ -1 +0,0 @@ -var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e){return await new Promise(((n,t)=>{(0,r.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?n(e.Slug):t(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("provision"),a=document.getElementById("slug"),d=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");l.addEventListener("submit",(e=>{e.preventDefault(),i({Slug:a.value,System:{PHP:d.value,OpCacheDevelopment:u.checked}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),l.querySelector("fieldset")?.removeAttribute("disabled"); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminProvision.d1b24c7b.js b/internal/dis/component/server/assets/dist/AdminProvision.d1b24c7b.js new file mode 100644 index 0000000..cf47e52 --- /dev/null +++ b/internal/dis/component/server/assets/dist/AdminProvision.d1b24c7b.js @@ -0,0 +1 @@ +var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},t={},n={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in t)return t[e].exports;if(e in n){var o=n[e];delete n[e];var r={id:e,exports:{}};return t[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,t){n[e]=t},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e){return await new Promise(((t,n)=>{(0,r.createModal)("provision",[JSON.stringify(e)],{bufferSize:0,onClose:(o,r)=>{o?t(e.Slug):n(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("provision"),a=document.getElementById("slug"),d=document.getElementById("php"),c=document.getElementById("opcacheDevelopment"),u=document.getElementById("contentsecuritypolicy");l.addEventListener("submit",(e=>{e.preventDefault(),i({Slug:a.value,System:{PHP:d.value,OpCacheDevelopment:c.checked,ContentSecurityPolicy:u.value}}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),l.querySelector("fieldset")?.removeAttribute("disabled"); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminRebuild.0149f285.js b/internal/dis/component/server/assets/dist/AdminRebuild.0149f285.js deleted file mode 100644 index 14efcc5..0000000 --- a/internal/dis/component/server/assets/dist/AdminRebuild.0149f285.js +++ /dev/null @@ -1 +0,0 @@ -var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function i(e,n){return await new Promise(((t,o)=>{(0,r.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(r??"unspecified error"))}})}))}const l=document.getElementById("slug"),a=document.getElementById("provision"),d=document.getElementById("php"),u=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),i(l.value,{PHP:d.value,OpCacheDevelopment:u.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),a.querySelector("fieldset")?.removeAttribute("disabled"); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminRebuild.6953ed8a.js b/internal/dis/component/server/assets/dist/AdminRebuild.6953ed8a.js deleted file mode 100644 index ab05a46..0000000 --- a/internal/dis/component/server/assets/dist/AdminRebuild.6953ed8a.js +++ /dev/null @@ -1 +0,0 @@ -!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var i=new Error("Cannot find module '"+e+"'");throw i.code="MODULE_NOT_FOUND",i}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,i=o("8vh0V");async function l(e,n){return await new Promise(((t,o)=>{(0,i.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("slug"),a=document.getElementById("provision"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment");a.addEventListener("submit",(e=>{e.preventDefault(),l(d.value,{PHP:u.value,OpCacheDevelopment:c.checked}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=a.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}(); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminRebuild.6eda9153.js b/internal/dis/component/server/assets/dist/AdminRebuild.6eda9153.js new file mode 100644 index 0000000..9bade75 --- /dev/null +++ b/internal/dis/component/server/assets/dist/AdminRebuild.6eda9153.js @@ -0,0 +1 @@ +var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},t={},n={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in t)return t[e].exports;if(e in n){var o=n[e];delete n[e];var r={id:e,exports:{}};return t[e]=r,o.call(r.exports,r,r.exports),r.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,t){n[e]=t},e.parcelRequireafa4=o),o("8xGhL");var r=o("12vpF");async function l(e,t){return await new Promise(((n,o)=>{(0,r.createModal)("rebuild",[e,JSON.stringify(t)],{bufferSize:0,onClose:(t,r)=>{t?n(e):o(new Error(r??"unspecified error"))}})}))}const i=document.getElementById("rebuild"),d=document.getElementById("slug"),a=document.getElementById("php"),c=document.getElementById("opcacheDevelopment"),u=document.getElementById("contentsecuritypolicy");i.addEventListener("submit",(e=>{e.preventDefault(),l(d.value,{PHP:a.value,OpCacheDevelopment:c.checked,ContentSecurityPolicy:u.value}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),i.querySelector("fieldset")?.removeAttribute("disabled"); \ No newline at end of file diff --git a/internal/dis/component/server/assets/dist/AdminRebuild.d9ab4bf2.js b/internal/dis/component/server/assets/dist/AdminRebuild.d9ab4bf2.js new file mode 100644 index 0000000..c10f50c --- /dev/null +++ b/internal/dis/component/server/assets/dist/AdminRebuild.d9ab4bf2.js @@ -0,0 +1 @@ +!function(){var e="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:{},n={},t={},o=e.parcelRequireafa4;null==o&&((o=function(e){if(e in n)return n[e].exports;if(e in t){var o=t[e];delete t[e];var r={id:e,exports:{}};return n[e]=r,o.call(r.exports,r,r.exports),r.exports}var l=new Error("Cannot find module '"+e+"'");throw l.code="MODULE_NOT_FOUND",l}).register=function(e,n){t[e]=n},e.parcelRequireafa4=o),o("dK5Bi");var r,l=o("8vh0V");async function i(e,n){return await new Promise(((t,o)=>{(0,l.createModal)("rebuild",[e,JSON.stringify(n)],{bufferSize:0,onClose:(n,r)=>{n?t(e):o(new Error(null!=r?r:"unspecified error"))}})}))}const d=document.getElementById("rebuild"),a=document.getElementById("slug"),u=document.getElementById("php"),c=document.getElementById("opcacheDevelopment"),f=document.getElementById("contentsecuritypolicy");d.addEventListener("submit",(e=>{e.preventDefault(),i(a.value,{PHP:u.value,OpCacheDevelopment:c.checked,ContentSecurityPolicy:f.value}).then((e=>{location.href="/admin/instance/"+e})).catch((e=>{console.error(e),location.reload()}))})),null===(r=d.querySelector("fieldset"))||void 0===r||r.removeAttribute("disabled")}(); \ No newline at end of file diff --git a/internal/dis/component/server/assets/src/entry/Admin/index.css b/internal/dis/component/server/assets/src/entry/Admin/index.css index 7490804..f2f1648 100644 --- a/internal/dis/component/server/assets/src/entry/Admin/index.css +++ b/internal/dis/component/server/assets/src/entry/Admin/index.css @@ -12,6 +12,8 @@ bottom: 1em; } +/* WissKI Status */ + .wisski.running { background-color: #9ADA07; } @@ -20,6 +22,16 @@ background-color: #ff7a7a; } +.disabled { + color: gray; +} + +.overflow { + overflow: auto; +} + +/* Informational Chips */ + .info-chip { min-width: 75px; height: 15px; @@ -27,4 +39,24 @@ display: block; padding: 5px; text-align: center; +} + +.info-chip.note { + background-color: gray; + color: #fff; +} + +.info-chip.info { + background-color: green; + color: #fff; +} + +.info-chip.warning { + background-color: yellow; + color: #000; +} + +.info-chip.error { + background-color: red; + color: #000; } \ No newline at end of file diff --git a/internal/dis/component/server/assets/src/entry/AdminProvision/index.ts b/internal/dis/component/server/assets/src/entry/AdminProvision/index.ts index 47c9d26..4608964 100644 --- a/internal/dis/component/server/assets/src/entry/AdminProvision/index.ts +++ b/internal/dis/component/server/assets/src/entry/AdminProvision/index.ts @@ -7,12 +7,13 @@ const provision = document.getElementById('provision') as HTMLFormElement const slug = document.getElementById('slug') as HTMLInputElement const php = document.getElementById('php') as HTMLSelectElement const opcacheDevelopment = document.getElementById('opcacheDevelopment') as HTMLInputElement +const contentSecurityPolicy = document.getElementById('contentsecuritypolicy') as HTMLInputElement // add an event handler to open the modal form! provision.addEventListener('submit', (evt) => { evt.preventDefault() - Provision({ Slug: slug.value, System: { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked } }) + Provision({ Slug: slug.value, System: { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked, ContentSecurityPolicy: contentSecurityPolicy.value } }) .then(slug => { location.href = '/admin/instance/' + slug }) diff --git a/internal/dis/component/server/assets/src/entry/AdminRebuild/index.ts b/internal/dis/component/server/assets/src/entry/AdminRebuild/index.ts index 71458f3..1a80401 100644 --- a/internal/dis/component/server/assets/src/entry/AdminRebuild/index.ts +++ b/internal/dis/component/server/assets/src/entry/AdminRebuild/index.ts @@ -3,16 +3,17 @@ import '../Admin/index.css' import { Rebuild } from '~/src/lib/remote/api' +const rebuild = document.getElementById('rebuild') as HTMLFormElement const slug = document.getElementById('slug') as HTMLInputElement -const provision = document.getElementById('provision') as HTMLFormElement const php = document.getElementById('php') as HTMLSelectElement const opcacheDevelopment = document.getElementById('opcacheDevelopment') as HTMLInputElement +const contentSecurityPolicy = document.getElementById('contentsecuritypolicy') as HTMLInputElement // add an event handler to open the modal form! -provision.addEventListener('submit', (evt) => { +rebuild.addEventListener('submit', (evt) => { evt.preventDefault() - Rebuild(slug.value, { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked }) + Rebuild(slug.value, { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked, ContentSecurityPolicy: contentSecurityPolicy.value }) .then(slug => { location.href = '/admin/instance/' + slug }) @@ -20,4 +21,4 @@ provision.addEventListener('submit', (evt) => { }) // enable the form! -provision.querySelector('fieldset')?.removeAttribute('disabled') +rebuild.querySelector('fieldset')?.removeAttribute('disabled') diff --git a/internal/dis/component/server/assets/src/lib/remote/api.ts b/internal/dis/component/server/assets/src/lib/remote/api.ts index 200780a..2beba1e 100644 --- a/internal/dis/component/server/assets/src/lib/remote/api.ts +++ b/internal/dis/component/server/assets/src/lib/remote/api.ts @@ -12,6 +12,7 @@ interface ProvisionFlags { interface System { PHP: string OpCacheDevelopment: boolean + ContentSecurityPolicy: string } /** Rebuild the specified instance */ diff --git a/internal/dis/component/server/server.go b/internal/dis/component/server/server.go index 2e0882b..ba3ddc5 100644 --- a/internal/dis/component/server/server.go +++ b/internal/dis/component/server/server.go @@ -8,6 +8,7 @@ import ( "github.com/FAU-CDI/wisski-distillery/internal/dis/component" "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating" + "github.com/FAU-CDI/wisski-distillery/internal/models" "github.com/tkw1536/pkglib/contextx" "github.com/tkw1536/pkglib/httpx" "github.com/tkw1536/pkglib/mux" @@ -107,6 +108,11 @@ func (server *Server) Server(ctx context.Context, progress io.Writer) (public ht // apply the given context function public = httpx.WithContextWrapper(&publicM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) }) internal = httpx.WithContextWrapper(&internalM, func(rcontext context.Context) context.Context { return contextx.WithValuesOf(rcontext, ctx) }) + + // Add Content-Security-Policy + public = WithCSP(public, models.ContentSecurityPolicyDistilery) + internal = WithCSP(internal, models.ContentSecurityPolicyNothing) + err = nil return } @@ -123,6 +129,17 @@ func (server *Server) csrf() func(http.Handler) http.Handler { return csrf.Protect(server.Config.CSRFSecret(), opts...) } +// WithCSP adds a Content-Security-Policy header to every response +func WithCSP(handler http.Handler, policy string) http.Handler { + if policy == "" { + return handler + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Security-Policy", policy) + handler.ServeHTTP(w, r) + }) +} + func init() { httpx.InterceptorOnFallback = func(req *http.Request, err error) { zerolog.Ctx(req.Context()).Err(err).Msg("unknown error intercepted") diff --git a/internal/models/instances_system.go b/internal/models/instances_system.go index aaadd0e..a9affe3 100644 --- a/internal/models/instances_system.go +++ b/internal/models/instances_system.go @@ -4,8 +4,10 @@ package models // It is embedded into the instances struct by gorm. type System struct { // NOTE(twiesing): Any changes here should be reflected in instance_{provision,rebuild}.html and remote/api.ts. - PHP string `gorm:"column:php;not null"` - OpCacheDevelopment bool `gorm:"column:opcache_devel;not null"` + PHP string `gorm:"column:php;not null"` // php version to use + OpCacheDevelopment bool `gorm:"column:opcache_devel;not null"` // opcache development + + ContentSecurityPolicy string `gorm:"column:csp;not null"` // content security policy for the system } const ( @@ -48,3 +50,17 @@ func (system System) GetDockerBaseImage() string { } return imagePrefix + version + imageSuffix } + +const ( + // Content Security Policy used by the internal server + ContentSecurityPolicyNothing = "base-uri 'self'; default-src 'none';" + + // Content Security policy used by the distillery admin server + ContentSecurityPolicyDistilery = "base-uri 'self'; default-src 'self'; img-src 'self' data:; media-src 'none'; worker-src 'none'; frame-src 'none'; frame-ancestors 'none';" +) + +func ContentSecurityPolicyExamples() []string { + return []string{ + ContentSecurityPolicyDistilery, + } +} diff --git a/internal/status/wisski.go b/internal/status/wisski.go index 196e8ba..de38b2b 100644 --- a/internal/status/wisski.go +++ b/internal/status/wisski.go @@ -59,13 +59,13 @@ type Requirement struct { func (req Requirement) Level() template.HTML { switch req.Severity { case -1: - return "Note" + return "Note" case 0: - return "Info" + return "Info" case 1: - return "Warning" + return "Warning" case 2: - return "Error" + return "Error" } return template.HTML(strconv.Itoa(req.Severity)) } diff --git a/internal/wisski/ingredient/barrel/barrel/.dockerignore b/internal/wisski/ingredient/barrel/barrel/.dockerignore index 6d594ed..dabff25 100644 --- a/internal/wisski/ingredient/barrel/barrel/.dockerignore +++ b/internal/wisski/ingredient/barrel/barrel/.dockerignore @@ -2,7 +2,7 @@ * # allow the following files: -!conf/* +!apache.d/* !scripts/* !ssh/* !php.ini.d/* \ No newline at end of file diff --git a/internal/wisski/ingredient/barrel/barrel/Dockerfile b/internal/wisski/ingredient/barrel/barrel/Dockerfile index 30dec67..49806c2 100644 --- a/internal/wisski/ingredient/barrel/barrel/Dockerfile +++ b/internal/wisski/ingredient/barrel/barrel/Dockerfile @@ -76,7 +76,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \ docker-php-source delete # enable the apache rewrite mod -RUN a2enmod rewrite +RUN a2enmod rewrite headers # Install composer. @@ -94,6 +94,8 @@ ADD php.ini.d/wisski.ini /usr/local/etc/php/conf.d/wisski.ini ARG OPCACHE_MODE=prod ADD php.ini.d/opcache-$OPCACHE_MODE.ini /usr/local/etc/php/conf.d/opcache.ini +ARG CONTENT_SECURITY_POLICY= +ENV CONTENT_SECURITY_POLICY=${CONTENT_SECURITY_POLICY} # Configure Apache. @@ -102,8 +104,8 @@ RUN rm /etc/apache2/sites-available/*.conf && \ rm /etc/apache2/sites-enabled/*.conf # Then add the WissKI site -ADD conf/ports.conf /etc/apache2/ports.conf -ADD conf/wisski.conf /etc/apache2/sites-available/wisski.conf +ADD apache.d/conf/ports.conf /etc/apache2/ports.conf +ADD apache.d/sites-available/wisski.conf /etc/apache2/sites-available/wisski.conf # And enable it RUN a2ensite wisski diff --git a/internal/wisski/ingredient/barrel/barrel/conf/ports.conf b/internal/wisski/ingredient/barrel/barrel/apache.d/conf/ports.conf similarity index 100% rename from internal/wisski/ingredient/barrel/barrel/conf/ports.conf rename to internal/wisski/ingredient/barrel/barrel/apache.d/conf/ports.conf diff --git a/internal/wisski/ingredient/barrel/barrel/conf/wisski.conf b/internal/wisski/ingredient/barrel/barrel/apache.d/sites-available/wisski.conf similarity index 74% rename from internal/wisski/ingredient/barrel/barrel/conf/wisski.conf rename to internal/wisski/ingredient/barrel/barrel/apache.d/sites-available/wisski.conf index 05ee47e..07a8439 100644 --- a/internal/wisski/ingredient/barrel/barrel/conf/wisski.conf +++ b/internal/wisski/ingredient/barrel/barrel/apache.d/sites-available/wisski.conf @@ -19,6 +19,11 @@ Require all granted + # Read the CONTENT_SECURITY_POLICY from the environment! + PassEnv CONTENT_SECURITY_POLICY + Header set Content-Security-Policy %{CONTENT_SECURITY_POLICY}e "expr=-n osenv('CONTENT_SECURITY_POLICY')" + + # Don't low to allow ignoring everything ErrorLog /dev/stderr CustomLog /dev/stdout combined diff --git a/internal/wisski/ingredient/barrel/barrel/docker-compose.yml b/internal/wisski/ingredient/barrel/barrel/docker-compose.yml index 6c3ade8..5b4f752 100644 --- a/internal/wisski/ingredient/barrel/barrel/docker-compose.yml +++ b/internal/wisski/ingredient/barrel/barrel/docker-compose.yml @@ -7,6 +7,8 @@ services: args: BARREL_BASE_IMAGE: ${BARREL_BASE_IMAGE} OPCACHE_MODE: ${OPCACHE_MODE} + CONTENT_SECURITY_POLICY: ${CONTENT_SECURITY_POLICY} + logging: driver: none diff --git a/internal/wisski/ingredient/barrel/stack.go b/internal/wisski/ingredient/barrel/stack.go index 15f79c8..038d530 100644 --- a/internal/wisski/ingredient/barrel/stack.go +++ b/internal/wisski/ingredient/barrel/stack.go @@ -31,8 +31,9 @@ func (barrel *Barrel) Stack() component.StackWithResources { "DATA_PATH": filepath.Join(barrel.FilesystemBase, "data"), "RUNTIME_DIR": barrel.Malt.Config.Paths.RuntimeDir(), - "BARREL_BASE_IMAGE": barrel.GetDockerBaseImage(), - "OPCACHE_MODE": barrel.OpCacheMode(), + "BARREL_BASE_IMAGE": barrel.GetDockerBaseImage(), + "OPCACHE_MODE": barrel.OpCacheMode(), + "CONTENT_SECURITY_POLICY": barrel.ContentSecurityPolicy, }, MakeDirs: []string{"data", ".composer"}, diff --git a/internal/wisski/ingredient/barrel/system/system.go b/internal/wisski/ingredient/barrel/system/system.go index 8fbed20..f9379fe 100644 --- a/internal/wisski/ingredient/barrel/system/system.go +++ b/internal/wisski/ingredient/barrel/system/system.go @@ -32,6 +32,8 @@ func (smanager *SystemManager) Apply(ctx context.Context, progress io.Writer, sy return err } + // TODO: Apply Content-Security-Policy! + // and rebuild return smanager.Dependencies.Barrel.Build(ctx, progress, start) }