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 @@
Slug
-
+
@@ -24,6 +24,10 @@
Opache Development Configuration
+
+ Content Security Policy
+
+
Profile
@@ -35,4 +39,9 @@
-
\ No newline at end of file
+
+
+ {{ range .ContentSecurityPolicies }}
+
+ {{ end }}
+
\ 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 @@
-
+
Opache Development Configuration
+
+ Content Security Policy
+
+
Profile
@@ -36,4 +40,10 @@
-
\ No newline at end of file
+
+
+
+ {{ range .ContentSecurityPolicies }}
+
+ {{ end }}
+
\ 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)
}