Add a customizable HTTP/HTTPS block page for sinkholed queries
Serves a page explaining why a domain was blocked instead of leaving a sinkholed client with a dead connection. Binds its own HTTP/HTTPS listeners with self-signed, per-hostname TLS certs generated on the fly, re-evaluates the requesting client against the policy engine per request, and renders an HTML template editable from Settings with a live preview. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TTKpGMQzpfDsvedu1hvSUf
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
{{define "content"}}
|
||||
{{$s := .Data.S}}
|
||||
|
||||
<h1 class="page-title mb-1">Settings</h1>
|
||||
<p class="text-body-secondary mb-4">Server configuration, stored in the database.</p>
|
||||
|
||||
{{template "settingsnav" .}}
|
||||
|
||||
<form method="post" action="/settings/blockpage" data-preview-url="/settings/blockpage/preview">
|
||||
<input type="hidden" name="_csrf" value="{{.CSRF}}">
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">Block page server</div>
|
||||
<div class="card-body">
|
||||
<div class="form-check form-switch mb-3">
|
||||
<input type="hidden" name="enabled" value="false">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="bpEnabled"
|
||||
name="enabled" value="true" {{if $s.BlockPage.Enabled}}checked{{end}}>
|
||||
<label class="form-check-label" for="bpEnabled">
|
||||
Serve a block page on HTTP/HTTPS {{template "restartbadge"}}
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text mb-3">
|
||||
Point a policy's sinkhole address at this host, and a browser that
|
||||
lands here instead of the real site sees the page below. Ports
|
||||
below 1024 need root or
|
||||
<span class="mono">CAP_NET_BIND_SERVICE</span>.
|
||||
</div>
|
||||
|
||||
<label class="form-label" for="bpHTTP">HTTP listen address</label>
|
||||
<input type="text" class="form-control mono mb-3" id="bpHTTP" name="http_listen"
|
||||
value="{{$s.BlockPage.HTTPListen}}" placeholder=":80" required>
|
||||
|
||||
<label class="form-label" for="bpHTTPS">HTTPS listen address</label>
|
||||
<input type="text" class="form-control mono mb-3" id="bpHTTPS" name="https_listen"
|
||||
value="{{$s.BlockPage.HTTPSListen}}" placeholder=":443" required>
|
||||
<div class="form-text">
|
||||
HTTPS is served with a self-signed certificate generated per
|
||||
hostname on the fly — browsers will show a trust warning, which
|
||||
is expected: this is not the real site's certificate.
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<span class="status-dot {{if .Data.Running}}status-up{{else}}status-down{{end}}"></span>
|
||||
<span class="fw-semibold">
|
||||
{{if .Data.Running}}Listeners running{{else}}Listeners stopped{{end}}
|
||||
</span>
|
||||
</div>
|
||||
<dl class="row small mb-0">
|
||||
<dt class="col-5 text-body-secondary">HTTP</dt>
|
||||
<dd class="col-7 mono">{{if .Data.BoundHTTP}}{{.Data.BoundHTTP}}{{else}}—{{end}}</dd>
|
||||
<dt class="col-5 text-body-secondary">HTTPS</dt>
|
||||
<dd class="col-7 mono">{{if .Data.BoundHTTPS}}{{.Data.BoundHTTPS}}{{else}}—{{end}}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Available placeholders</div>
|
||||
<div class="card-body">
|
||||
<p class="text-body-secondary small mb-2">
|
||||
Filled in per request by re-evaluating the requesting client
|
||||
against the policy engine, the same way a DNS query would be.
|
||||
</p>
|
||||
<dl class="row small mb-0">
|
||||
<dt class="col-5 mono">{{"{{.Domain}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Requested hostname</dd>
|
||||
<dt class="col-5 mono">{{"{{.ListName}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Matched blocklist name</dd>
|
||||
<dt class="col-5 mono">{{"{{.MatchedDomain}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Matched list entry</dd>
|
||||
<dt class="col-5 mono">{{"{{.PolicyName}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Matched policy name</dd>
|
||||
<dt class="col-5 mono">{{"{{.NetworkName}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Matched client network</dd>
|
||||
<dt class="col-5 mono">{{"{{.ClientIP}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Requesting address</dd>
|
||||
<dt class="col-5 mono">{{"{{.RequestPath}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">URL path requested</dd>
|
||||
<dt class="col-5 mono">{{"{{.Scheme}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">http or https</dd>
|
||||
<dt class="col-5 mono">{{"{{.Timestamp}}"}}</dt>
|
||||
<dd class="col-7 text-body-secondary">Time of the request</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">HTML editor</div>
|
||||
<div class="card-body">
|
||||
<textarea class="form-control mono" id="blockPageHTML" name="html" rows="22"
|
||||
spellcheck="false" style="font-size:.85rem">{{$s.BlockPage.HTML}}</textarea>
|
||||
<div class="form-text">
|
||||
Standard Go template syntax. A broken template falls back to a
|
||||
plain built-in page rather than breaking the listener.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Preview</div>
|
||||
<div class="card-body p-0">
|
||||
<iframe id="blockPagePreview" title="Block page preview"
|
||||
style="width:100%;height:480px;border:0;border-radius:0 0 .5rem .5rem;background:#fff"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg me-1"></i>Save block page settings
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user