More web interface pages
This commit is contained in:
@@ -1547,7 +1547,7 @@
|
|||||||
} }, ['debug', 'info', 'warn', 'error'].map((l) => h('option', { value: l, selected: s.log.level === l }, l)));
|
} }, ['debug', 'info', 'warn', 'error'].map((l) => h('option', { value: l, selected: s.log.level === l }, l)));
|
||||||
|
|
||||||
// decoy
|
// decoy
|
||||||
const decoyPages = [['nginx', 'nginx welcome page'], ['apache', 'Apache "It works!" page'], ['soon', '"Coming soon" page']];
|
const decoyPages = [['nginx', 'nginx welcome page'], ['apache', 'Apache "It works!" page'], ['soon', '"Coming soon" page'], ['blank', 'Blank page'], ['forbidden', '"Forbidden" page'], ['private', '"Private server" page']];
|
||||||
const decoyBox = h('input', { type: 'checkbox', id: 'dc', checked: s.decoy.enabled, onChange: async (e) => {
|
const decoyBox = h('input', { type: 'checkbox', id: 'dc', checked: s.decoy.enabled, onChange: async (e) => {
|
||||||
const on = e.target.checked;
|
const on = e.target.checked;
|
||||||
if (on) {
|
if (on) {
|
||||||
|
|||||||
@@ -11,15 +11,20 @@ import (
|
|||||||
// front page is its stock welcome page and everything else is its stock
|
// front page is its stock welcome page and everything else is its stock
|
||||||
// error page. Only /api/v1 and live setup links get past it.
|
// error page. Only /api/v1 and live setup links get past it.
|
||||||
type decoyPage struct {
|
type decoyPage struct {
|
||||||
server string // Server header, "" for none
|
server string // Server header, "" for none
|
||||||
index func(host string) string // the front page
|
index func(host string) string // the front page
|
||||||
error func(code int, r *http.Request) string // body for 404 and 405
|
indexCode int // status of the front page, 0 for 200
|
||||||
|
error func(code int, r *http.Request) string // body for 404 and 405
|
||||||
}
|
}
|
||||||
|
|
||||||
var decoyPages = map[string]decoyPage{
|
var decoyPages = map[string]decoyPage{
|
||||||
"nginx": {server: nginxServer, index: func(string) string { return nginxIndex }, error: nginxError},
|
"nginx": {server: nginxServer, index: func(string) string { return nginxIndex }, error: nginxError},
|
||||||
"apache": {server: apacheServer, index: func(string) string { return apacheIndex }, error: apacheError},
|
"apache": {server: apacheServer, index: func(string) string { return apacheIndex }, error: apacheError},
|
||||||
"soon": {index: soonIndex, error: soonError},
|
"soon": {index: soonIndex, error: soonError},
|
||||||
|
// Generic pages that name no server software.
|
||||||
|
"blank": {index: func(string) string { return "" }, error: func(int, *http.Request) string { return "" }},
|
||||||
|
"forbidden": {index: func(string) string { return forbiddenIndex }, indexCode: http.StatusForbidden, error: soonError},
|
||||||
|
"private": {index: func(string) string { return privateIndex }, error: soonError},
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveDecoy writes the decoy's answer for r. It drops the headers the web
|
// serveDecoy writes the decoy's answer for r. It drops the headers the web
|
||||||
@@ -43,6 +48,9 @@ func serveDecoy(w http.ResponseWriter, r *http.Request, name string) {
|
|||||||
code, body = http.StatusMethodNotAllowed, d.error(http.StatusMethodNotAllowed, r)
|
code, body = http.StatusMethodNotAllowed, d.error(http.StatusMethodNotAllowed, r)
|
||||||
case r.URL.Path == "/" || r.URL.Path == "/index.html":
|
case r.URL.Path == "/" || r.URL.Path == "/index.html":
|
||||||
body = d.index(hostOnly(r.Host))
|
body = d.index(hostOnly(r.Host))
|
||||||
|
if d.indexCode != 0 {
|
||||||
|
code = d.indexCode
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
code, body = http.StatusNotFound, d.error(http.StatusNotFound, r)
|
code, body = http.StatusNotFound, d.error(http.StatusNotFound, r)
|
||||||
}
|
}
|
||||||
@@ -537,3 +545,36 @@ const apacheIndex = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const forbiddenIndex = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>403 Forbidden</title></head>
|
||||||
|
<body>
|
||||||
|
<h1>Forbidden</h1>
|
||||||
|
<p>You don't have permission to access this resource.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
const privateIndex = `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Private</title>
|
||||||
|
<style>
|
||||||
|
html, body { height: 100%; margin: 0; }
|
||||||
|
body { display: flex; align-items: center; justify-content: center; background: #111; color: #999;
|
||||||
|
font-family: Georgia, serif; text-align: center; }
|
||||||
|
h1 { font-size: 28px; font-weight: normal; letter-spacing: 0.04em; color: #fff; margin: 0 0 10px; }
|
||||||
|
p { margin: 0; font-size: 15px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Private server</h1>
|
||||||
|
<p>Nothing to see here.</p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|||||||
@@ -954,6 +954,21 @@ func TestDecoy(t *testing.T) {
|
|||||||
if b, h := get("/", 200); !strings.Contains(b, "<p class=\"host\">127.0.0.1</p>") || h.Get("Server") != "" {
|
if b, h := get("/", 200); !strings.Contains(b, "<p class=\"host\">127.0.0.1</p>") || h.Get("Server") != "" {
|
||||||
t.Fatalf("soon decoy: %q", b)
|
t.Fatalf("soon decoy: %q", b)
|
||||||
}
|
}
|
||||||
|
set(func(c *Config) { c.Decoy.Page = "blank" })
|
||||||
|
if b, _ := get("/", 200); b != "" {
|
||||||
|
t.Fatalf("blank decoy: %q", b)
|
||||||
|
}
|
||||||
|
if b, _ := get("/app.js", 404); b != "" {
|
||||||
|
t.Fatalf("blank 404: %q", b)
|
||||||
|
}
|
||||||
|
set(func(c *Config) { c.Decoy.Page = "forbidden" })
|
||||||
|
if b, _ := get("/", 403); !strings.Contains(b, "Forbidden") {
|
||||||
|
t.Fatalf("forbidden decoy: %q", b)
|
||||||
|
}
|
||||||
|
set(func(c *Config) { c.Decoy.Page = "private" })
|
||||||
|
if b, _ := get("/", 200); !strings.Contains(b, "Private server") {
|
||||||
|
t.Fatalf("private decoy: %q", b)
|
||||||
|
}
|
||||||
if err := store.Update(func(c *Config) error { c.Decoy.Page = "iis"; return nil }); err == nil {
|
if err := store.Update(func(c *Config) error { c.Decoy.Page = "iis"; return nil }); err == nil {
|
||||||
t.Fatal("unknown decoy page accepted")
|
t.Fatal("unknown decoy page accepted")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user