Add support for Content-Security-Policy in images

This commit adds support for the "Content-Security-Policy" header in the
barrel images.
This commit is contained in:
Tom 2023-07-13 23:26:37 +02:00
parent 17d64826df
commit 760aae0dc1
32 changed files with 162 additions and 48 deletions

View file

@ -15,6 +15,7 @@ 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"`
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"`
@ -43,6 +44,7 @@ func (p pv) Run(context wisski_distillery.Context) error {
System: models.System{
PHP: p.PHPVersion,
OpCacheDevelopment: p.OPCacheDevelopment,
ContentSecurityPolicy: p.ContentSecurityPolicy,
},
})
if err != nil {

View file

@ -20,6 +20,8 @@ type rebuild 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)

View file

@ -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() {

View file

@ -120,6 +120,14 @@
<code>{{ .Instance.System.OpCacheDevelopment }}</code>
</td>
</tr>
<tr>
<td>
Content Security Policy
</td>
<td>
<code>{{ .Instance.System.ContentSecurityPolicy }}</code>
</td>
</tr>
</tbody>
</table>
</div>
@ -143,7 +151,7 @@
Directory
</td>
<td>
<code style="overflow: auto;">{{ .Instance.FilesystemBase }}</code>
<code class="overflow">{{ .Instance.FilesystemBase }}</code>
</td>
</tr>
<tr>
@ -351,7 +359,7 @@
{{ $slug := .Instance.Slug }}
{{ $csrf := .CSRF }}
{{ range $index, $user := .Info.Users }}
<tr {{ if not $user.Status }}style="color:gray" aria-disabled="true"{{ end }}>
<tr {{ if not $user.Status }}class="disabled" aria-disabled="true"{{ end }}>
<td>
<code>{{ $user.UID }}</code>
</td>

View file

@ -5,7 +5,7 @@
<div class="pure-control-group">
<label for="slug">Slug</label>
<input name="slug" id="slug" placeholder="" autocomplete="slug">
<input name="slug" id="slug" placeholder="">
</div>
</fieldset>
<fieldset>
@ -24,6 +24,10 @@
<input type="checkbox" id="opcacheDevelopment" />
Opache Development Configuration
</label>
<div class="pure-control-group">
<label for="contentsecuritypolicy">Content Security Policy</label>
<input class="pure-input-1" name="contentsecuritypolicy" id="contentsecuritypolicy" list="content-security-policy" placeholder="">
</div>
</fieldset>
<fieldset>
<legend>Profile</legend>
@ -36,3 +40,8 @@
<input type="submit" value="Provision" class="pure-button">
</form>
</div>
<datalist id="content-security-policy">
{{ range .ContentSecurityPolicies }}
<option value="{{ . }}">
{{ end }}
</datalist>

View file

@ -1,11 +1,11 @@
<div class="pure-u-1-1">
<form class="pure-form pure-form-aligned" id="provision">
<form class="pure-form pure-form-aligned" id="rebuild">
<fieldset>
<legend>Main Parameters</legend>
<div class="pure-control-group">
<label for="slug">Slug</label>
<input name="slug" id="slug" placeholder="" autocomplete="slug" readonly="readonly" value="{{ .Slug }}">
<input name="slug" id="slug" placeholder="" readonly="readonly" value="{{ .Slug }}">
</div>
</fieldset>
<fieldset>
@ -22,9 +22,13 @@
</select>
</div>
<label for="opcacheDevelopment" class="pure-checkbox">
<input {{ if .System.OpCacheDevelopment }}checked{{end}} type="checkbox" id="opcacheDevelopment" check/>
<input {{ if .System.OpCacheDevelopment }}checked{{end}} type="checkbox" id="opcacheDevelopment"/>
Opache Development Configuration
</label>
<div class="pure-control-group" class="pure-input-1">
<label for="contentsecuritypolicy">Content Security Policy</label>
<input name="contentsecuritypolicy" id="contentsecuritypolicy" list="content-security-policy" value="{{ .System.ContentSecurityPolicy }}">
</div>
</fieldset>
<fieldset>
<legend>Profile</legend>
@ -37,3 +41,9 @@
<input type="submit" value="Rebuild" class="pure-button">
</form>
</div>
<datalist id="content-security-policy">
{{ range .ContentSecurityPolicies }}
<option value="{{ . }}">
{{ end }}
</datalist>

View file

@ -37,7 +37,7 @@
<tbody>
{{ $csrf := .CSRF }}
{{ range .Users }}
<tr {{ if not .User.IsEnabled }}style="color:gray"{{ end }}>
<tr {{ if not .User.IsEnabled }}class="disabled"{{ end }}>
<td>
{{ .User.User }}
</td>

View file

@ -28,11 +28,13 @@ type instanceProvisionContext struct {
type systemParams struct {
PHPVersions []string
ContentSecurityPolicies []string
DefaultPHPVersion string
}
func newSystemParams() (sp systemParams) {
sp.PHPVersions = models.KnownPHPVersions()
sp.ContentSecurityPolicies = models.ContentSecurityPolicyExamples()
sp.DefaultPHPVersion = models.DefaultPHPVersion
return sp
}

View file

@ -25,17 +25,17 @@ var AssetsUser = Assets{
// AssetsAdmin contains assets for the 'Admin' entrypoint.
var AssetsAdmin = Assets{
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script src="/⛰/Admin.1b10eebb.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css">`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.78d18bfa.css">`,
}
// AssetsAdminProvision contains assets for the 'AdminProvision' entrypoint.
var AssetsAdminProvision = Assets{
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminProvision.9cbda41c.js"></script><script src="/⛰/AdminProvision.68dbff79.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css"><link rel="stylesheet" href="/⛰/AdminProvision.38d394c2.css">`,
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminProvision.d1b24c7b.js"></script><script src="/⛰/AdminProvision.0b361a8e.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.78d18bfa.css"><link rel="stylesheet" href="/⛰/AdminProvision.38d394c2.css">`,
}
// AssetsAdminRebuild contains assets for the 'AdminRebuild' entrypoint.
var AssetsAdminRebuild = Assets{
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminRebuild.0149f285.js"></script><script src="/⛰/AdminRebuild.6953ed8a.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.d79c7b30.css"><link rel="stylesheet" href="/⛰/AdminRebuild.38d394c2.css">`,
Scripts: `<script nomodule="" defer src="/⛰/User.e4c5f849.js"></script><script nomodule="" defer src="/⛰/Admin.1b10eebb.js"></script><script type="module" src="/⛰/User.fce9a3e3.js"></script><script type="module" src="/⛰/Admin.87f202f8.js"></script><script type="module" src="/⛰/Default.38d394c2.js"></script><script src="/⛰/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/⛰/AdminRebuild.6eda9153.js"></script><script src="/⛰/AdminRebuild.d9ab4bf2.js" nomodule="" defer></script>`,
Styles: `<link rel="stylesheet" href="/⛰/Default.938b4407.css"><link rel="stylesheet" href="/⛰/Admin.a1e05c23.css"><link rel="stylesheet" href="/⛰/User.840de3b4.css"><link rel="stylesheet" href="/⛰/User.68febbf8.css"><link rel="stylesheet" href="/⛰/Admin.78d18bfa.css"><link rel="stylesheet" href="/⛰/AdminRebuild.38d394c2.css">`,
}

View file

@ -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}

View file

@ -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}

View file

@ -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")}();
!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")}();

View file

@ -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");

View file

@ -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");

View file

@ -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");

View file

@ -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")}();

View file

@ -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");

View file

@ -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")}();

View file

@ -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;
@ -28,3 +40,23 @@
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;
}

View file

@ -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
})

View file

@ -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')

View file

@ -12,6 +12,7 @@ interface ProvisionFlags {
interface System {
PHP: string
OpCacheDevelopment: boolean
ContentSecurityPolicy: string
}
/** Rebuild the specified instance */

View file

@ -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")

View file

@ -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,
}
}

View file

@ -59,13 +59,13 @@ type Requirement struct {
func (req Requirement) Level() template.HTML {
switch req.Severity {
case -1:
return "<span style='background-color: gray; color: #fff;' class='info-chip'>Note</span>"
return "<span class='info-chip note'>Note</span>"
case 0:
return "<span style='background-color: green; color: #fff;' class='info-chip'>Info</span>"
return "<span class='info-chip info'>Info</span>"
case 1:
return "<span style='background-color: yellow; color: #000;' class='info-chip'>Warning</span>"
return "<span class='info-chip warning'>Warning</span>"
case 2:
return "<span style='background-color: red; color: #000;' class='info-chip'>Error</span>"
return "<span class='info-chip error'>Error</span>"
}
return template.HTML(strconv.Itoa(req.Severity))
}

View file

@ -2,7 +2,7 @@
*
# allow the following files:
!conf/*
!apache.d/*
!scripts/*
!ssh/*
!php.ini.d/*

View file

@ -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

View file

@ -19,6 +19,11 @@
Require all granted
</Directory>
# 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
</VirtualHost>

View file

@ -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

View file

@ -33,6 +33,7 @@ func (barrel *Barrel) Stack() component.StackWithResources {
"BARREL_BASE_IMAGE": barrel.GetDockerBaseImage(),
"OPCACHE_MODE": barrel.OpCacheMode(),
"CONTENT_SECURITY_POLICY": barrel.ContentSecurityPolicy,
},
MakeDirs: []string{"data", ".composer"},

View file

@ -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)
}