feat: add secure Pi-hole DNS management
Container-Image veröffentlichen / container (push) Has been cancelled
Container-Image veröffentlichen / container (push) Has been cancelled
This commit is contained in:
1 parent
1a6b4d0939
commit
1c32ca0671
18 files changed
+2159
-34
No files matched your search
+43
-4
@@ -57,7 +57,10 @@ type viewData struct {
|
||||
ArchivedTargets []model.Target
|
||||
CNAMERecords []model.CNAMERecord
|
||||
Settings model.Settings
|
||||
Token string
|
||||
SecretTitle string
|
||||
SecretScope string
|
||||
SecretValue string
|
||||
SecretBackURL string
|
||||
Form map[string]string
|
||||
Users []model.User
|
||||
ManagedUser model.User
|
||||
@@ -68,7 +71,9 @@ type viewData struct {
|
||||
ErrorTargets []model.Target
|
||||
ErrorZones []model.Zone
|
||||
ErrorRuns []model.UpdateRun
|
||||
ErrorPiHoles []model.PiHoleIntegration
|
||||
ErrorCount int
|
||||
PiHoleView *piHoleView
|
||||
}
|
||||
|
||||
type flashMessage struct {
|
||||
@@ -124,6 +129,18 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.Handle("POST /cnames/{id}/save", s.adminOnly(s.cnameSave))
|
||||
mux.Handle("POST /cnames/{id}/remove", s.adminOnly(s.cnameRemove))
|
||||
mux.Handle("POST /cnames/{id}/delete", s.adminOnly(s.cnameDelete))
|
||||
mux.Handle("GET /pihole", s.adminOnly(s.piHolePage))
|
||||
mux.Handle("POST /pihole/connect", s.adminOnly(s.piHoleConnect))
|
||||
mux.Handle("POST /pihole/{id}/save", s.adminOnly(s.piHoleSave))
|
||||
mux.Handle("POST /pihole/{id}/password", s.adminOnly(s.piHolePassword))
|
||||
mux.Handle("POST /pihole/{id}/refresh", s.adminOnly(s.piHoleRefresh))
|
||||
mux.Handle("POST /pihole/{id}/remove", s.adminOnly(s.piHoleRemove))
|
||||
mux.Handle("POST /pihole/{id}/delete", s.adminOnly(s.piHoleDelete))
|
||||
mux.Handle("POST /pihole/{id}/records", s.adminOnly(s.piHoleRecordCreate))
|
||||
mux.Handle("POST /pihole/{id}/records/update", s.adminOnly(s.piHoleRecordUpdate))
|
||||
mux.Handle("POST /pihole/{id}/records/delete", s.adminOnly(s.piHoleRecordDelete))
|
||||
mux.Handle("POST /pihole/{id}/records/archive", s.adminOnly(s.piHoleRecordArchive))
|
||||
mux.Handle("POST /pihole/{id}/records/restore", s.adminOnly(s.piHoleRecordRestore))
|
||||
mux.Handle("GET /settings", s.adminOnly(s.settingsPage))
|
||||
mux.Handle("POST /settings", s.adminOnly(s.settingsSave))
|
||||
mux.Handle("POST /run", s.adminOnly(s.runNow))
|
||||
@@ -640,7 +657,18 @@ func (s *Server) domainToken(w http.ResponseWriter, r *http.Request, session *mo
|
||||
}
|
||||
_ = s.store.SetSessionReauth(r.Context(), session.ID, time.Now().UTC().Add(5*time.Minute))
|
||||
_ = s.store.AddAudit(r.Context(), "credential.revealed", "API-Token angezeigt: "+zone.Name)
|
||||
s.render(w, "token", viewData{Title: "API-Token anzeigen", Page: "domains", CSRF: session.CSRFToken, Zone: zone, Token: token, User: userFromSession(session), IsAdmin: true, Now: time.Now()})
|
||||
s.render(w, "token", viewData{
|
||||
Title: "API-Token anzeigen",
|
||||
Page: "domains",
|
||||
CSRF: session.CSRFToken,
|
||||
SecretTitle: "Hetzner API-Token",
|
||||
SecretScope: "Zone: " + zone.Name,
|
||||
SecretValue: token,
|
||||
SecretBackURL: domainURL(zone.ID),
|
||||
User: userFromSession(session),
|
||||
IsAdmin: true,
|
||||
Now: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) targetCreate(w http.ResponseWriter, r *http.Request, session *model.Session) {
|
||||
@@ -1228,7 +1256,12 @@ func (s *Server) errorsPage(w http.ResponseWriter, r *http.Request, session *mod
|
||||
s.serverError(w, err)
|
||||
return
|
||||
}
|
||||
s.render(w, "errors", viewData{Title: "Fehler", Page: "errors", CSRF: session.CSRFToken, Flash: notice(r), User: userFromSession(session), IsAdmin: true, ErrorTargets: targets, ErrorZones: zones, ErrorRuns: runs, ErrorCount: len(targets) + len(zones) + len(runs), Now: time.Now()})
|
||||
piHoles, err := s.store.ListPiHoleIntegrationsWithErrors(r.Context())
|
||||
if err != nil {
|
||||
s.serverError(w, err)
|
||||
return
|
||||
}
|
||||
s.render(w, "errors", viewData{Title: "Fehler", Page: "errors", CSRF: session.CSRFToken, Flash: notice(r), User: userFromSession(session), IsAdmin: true, ErrorTargets: targets, ErrorZones: zones, ErrorRuns: runs, ErrorPiHoles: piHoles, ErrorCount: len(targets) + len(zones) + len(runs) + len(piHoles), Now: time.Now()})
|
||||
}
|
||||
|
||||
func (s *Server) errorsClear(w http.ResponseWriter, r *http.Request, session *model.Session) {
|
||||
@@ -1727,16 +1760,21 @@ func (s *Server) redirectNotice(w http.ResponseWriter, r *http.Request, target,
|
||||
func (s *Server) securityHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data:; object-src 'none'; script-src 'self'; style-src 'self'")
|
||||
w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
|
||||
w.Header().Set("Cross-Origin-Resource-Policy", "same-origin")
|
||||
w.Header().Set("Referrer-Policy", "no-referrer")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-Frame-Options", "DENY")
|
||||
w.Header().Set("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
||||
if s.config.CookieSecure {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func loadTemplates() (map[string]*template.Template, error) {
|
||||
pages := []string{"setup", "login", "dashboard", "domains", "domain_new", "domain_detail", "settings", "errors", "security", "token", "users", "user_new", "user_detail"}
|
||||
pages := []string{"setup", "login", "dashboard", "domains", "domain_new", "domain_detail", "pihole", "settings", "errors", "security", "token", "users", "user_new", "user_detail"}
|
||||
functions := template.FuncMap{
|
||||
"formatTime": func(value time.Time) string {
|
||||
if value.IsZero() {
|
||||
@@ -1755,6 +1793,7 @@ func loadTemplates() (map[string]*template.Template, error) {
|
||||
"statusClass": statusClass,
|
||||
"ttlOptions": func() []int { return []int{60, 300, 600, 1800, 3600, 86400} },
|
||||
"supportedTTL": supportedTTL,
|
||||
"contains": strings.Contains,
|
||||
"families": func(target model.Target) string {
|
||||
var result []string
|
||||
if target.UpdateIPv4 {
|
||||
|
||||
Reference in new issue
Block a user