v9.4.0
This commit is contained in:
+370
-2
@@ -13,6 +13,7 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/audit"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/auth"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/composeedit"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/config"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/gitops"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/hostsecurity"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/monitor"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/nodes"
|
||||
"git.send.nrw/sendnrw/dockwatch/internal/notify"
|
||||
@@ -37,10 +39,11 @@ type Server struct {
|
||||
audit *audit.Service
|
||||
notify *notify.Service
|
||||
git *gitops.Service
|
||||
security *hostsecurity.Service
|
||||
}
|
||||
|
||||
func New(c config.Config, a *auth.Service, ss *stacks.Service, n *nodes.Manager, m *monitor.Service, au *audit.Service, nt *notify.Service, gs *gitops.Service) http.Handler {
|
||||
s := &Server{cfg: c, auth: a, stacks: ss, nodes: n, monitors: m, audit: au, notify: nt, git: gs}
|
||||
func New(c config.Config, a *auth.Service, ss *stacks.Service, n *nodes.Manager, m *monitor.Service, au *audit.Service, nt *notify.Service, gs *gitops.Service, hs *hostsecurity.Service) http.Handler {
|
||||
s := &Server{cfg: c, auth: a, stacks: ss, nodes: n, monitors: m, audit: au, notify: nt, git: gs, security: hs}
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
jsonOut(w, 200, map[string]any{"ok": true, "mode": c.Mode, "build": buildinfo.Current()})
|
||||
@@ -129,6 +132,18 @@ func New(c config.Config, a *auth.Service, ss *stacks.Service, n *nodes.Manager,
|
||||
api.Handle("PUT /api/nodes/{id}", auth.RequireRole("admin", http.HandlerFunc(s.updateNode)))
|
||||
api.Handle("DELETE /api/nodes/{id}", auth.RequireRole("admin", http.HandlerFunc(s.deleteNode)))
|
||||
api.HandleFunc("GET /api/nodes/{id}/health", s.nodeHealth)
|
||||
api.Handle("GET /api/security/status", auth.RequireRole("admin", http.HandlerFunc(s.securityStatus)))
|
||||
api.Handle("GET /api/security/firewall", auth.RequireRole("admin", http.HandlerFunc(s.securityFirewall)))
|
||||
api.Handle("POST /api/security/firewall/preview", auth.RequireRole("admin", http.HandlerFunc(s.securityFirewallPreview)))
|
||||
api.Handle("POST /api/security/firewall/apply", auth.RequireRole("admin", http.HandlerFunc(s.securityFirewallApply)))
|
||||
api.Handle("POST /api/security/firewall/commit", auth.RequireRole("admin", http.HandlerFunc(s.securityFirewallCommit)))
|
||||
api.Handle("POST /api/security/firewall/rollback", auth.RequireRole("admin", http.HandlerFunc(s.securityFirewallRollback)))
|
||||
api.Handle("GET /api/security/fail2ban", auth.RequireRole("admin", http.HandlerFunc(s.securityFail2Ban)))
|
||||
api.Handle("PUT /api/security/fail2ban", auth.RequireRole("admin", http.HandlerFunc(s.securityApplyFail2Ban)))
|
||||
api.Handle("GET /api/security/auditd", auth.RequireRole("admin", http.HandlerFunc(s.securityAuditd)))
|
||||
api.Handle("PUT /api/security/auditd", auth.RequireRole("admin", http.HandlerFunc(s.securityApplyAuditd)))
|
||||
api.Handle("POST /api/security/components/{component}/install", auth.RequireRole("admin", http.HandlerFunc(s.securityInstall)))
|
||||
api.Handle("POST /api/security/components/{component}/actions/{action}", auth.RequireRole("admin", http.HandlerFunc(s.securityComponentAction)))
|
||||
mux.Handle("/api/", a.Middleware(mutationOriginGuard(s.auditMiddleware(api))))
|
||||
assets, _ := fs.Sub(web.FS, ".")
|
||||
f := http.FileServer(http.FS(assets))
|
||||
@@ -149,6 +164,18 @@ func (s *Server) agent(m *http.ServeMux) {
|
||||
a.HandleFunc("POST /agent/v1/docker/containers/{id}/bind-permissions/preview", s.localBindPermissionPreview)
|
||||
a.HandleFunc("POST /agent/v1/host/bind-permissions/repair", s.localRepairBindPermissions)
|
||||
a.HandleFunc("POST /agent/v1/host/users", s.localCreateHostUser)
|
||||
a.HandleFunc("GET /agent/v1/security/status", s.localSecurityStatus)
|
||||
a.HandleFunc("GET /agent/v1/security/firewall", s.localSecurityFirewall)
|
||||
a.HandleFunc("POST /agent/v1/security/firewall/preview", s.localSecurityFirewallPreview)
|
||||
a.HandleFunc("POST /agent/v1/security/firewall/apply", s.localSecurityFirewallApply)
|
||||
a.HandleFunc("POST /agent/v1/security/firewall/commit", s.localSecurityFirewallCommit)
|
||||
a.HandleFunc("POST /agent/v1/security/firewall/rollback", s.localSecurityFirewallRollback)
|
||||
a.HandleFunc("GET /agent/v1/security/fail2ban", s.localSecurityFail2Ban)
|
||||
a.HandleFunc("PUT /agent/v1/security/fail2ban", s.localSecurityApplyFail2Ban)
|
||||
a.HandleFunc("GET /agent/v1/security/auditd", s.localSecurityAuditd)
|
||||
a.HandleFunc("PUT /agent/v1/security/auditd", s.localSecurityApplyAuditd)
|
||||
a.HandleFunc("POST /agent/v1/security/components/{component}/install", s.localSecurityInstall)
|
||||
a.HandleFunc("POST /agent/v1/security/components/{component}/actions/{action}", s.localSecurityComponentAction)
|
||||
a.HandleFunc("GET /agent/v1/stacks", s.localList)
|
||||
a.HandleFunc("GET /agent/v1/stacks/{name}", s.localGet)
|
||||
a.HandleFunc("PUT /agent/v1/stacks/{name}", s.localSave)
|
||||
@@ -558,6 +585,19 @@ func (s *Server) relay(w http.ResponseWriter, r *http.Request, id int64, method,
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
func (s *Server) relayWithTimeout(w http.ResponseWriter, r *http.Request, id int64, method, path string, body any, timeout time.Duration) {
|
||||
b, status, e := s.nodes.DoWithTimeout(r.Context(), id, method, path, body, timeout)
|
||||
if e != nil {
|
||||
if status == 0 {
|
||||
status = 502
|
||||
}
|
||||
http.Error(w, e.Error(), status)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
func (s *Server) dockerInventory(w http.ResponseWriter, r *http.Request) {
|
||||
kind := r.PathValue("kind")
|
||||
if id := nodeID(r); id > 0 {
|
||||
@@ -1384,3 +1424,331 @@ func (s *Server) proxyTerminal(w http.ResponseWriter, r *http.Request, id int64)
|
||||
case <-done:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) securityStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodGet, "/agent/v1/security/status", nil)
|
||||
return
|
||||
}
|
||||
s.localSecurityStatus(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) localSecurityStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
jsonOut(w, http.StatusOK, s.security.Status(r.Context()))
|
||||
}
|
||||
|
||||
func (s *Server) securityFirewall(w http.ResponseWriter, r *http.Request) {
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodGet, "/agent/v1/security/firewall", nil)
|
||||
return
|
||||
}
|
||||
s.localSecurityFirewall(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) localSecurityFirewall(w http.ResponseWriter, r *http.Request) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
jsonOut(w, http.StatusOK, s.security.FirewallPolicy())
|
||||
}
|
||||
|
||||
func (s *Server) securityFirewallPreview(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.FirewallPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPost, "/agent/v1/security/firewall/preview", in)
|
||||
return
|
||||
}
|
||||
s.securityFirewallPreviewLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) localSecurityFirewallPreview(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.FirewallPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityFirewallPreviewLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) securityFirewallPreviewLocal(w http.ResponseWriter, r *http.Request, in hostsecurity.FirewallPolicy) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.PreviewFirewall(r.Context(), in)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
func (s *Server) securityFirewallApply(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
Policy hostsecurity.FirewallPolicy `json:"policy"`
|
||||
RollbackSeconds int `json:"rollback_seconds"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPost, "/agent/v1/security/firewall/apply", in)
|
||||
return
|
||||
}
|
||||
s.securityFirewallApplyLocal(w, r, in.Policy, in.RollbackSeconds)
|
||||
}
|
||||
func (s *Server) localSecurityFirewallApply(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
Policy hostsecurity.FirewallPolicy `json:"policy"`
|
||||
RollbackSeconds int `json:"rollback_seconds"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityFirewallApplyLocal(w, r, in.Policy, in.RollbackSeconds)
|
||||
}
|
||||
func (s *Server) securityFirewallApplyLocal(w http.ResponseWriter, r *http.Request, p hostsecurity.FirewallPolicy, seconds int) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.ApplyFirewall(r.Context(), p, seconds)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
func (s *Server) securityFirewallCommit(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
ChangeID string `json:"change_id"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPost, "/agent/v1/security/firewall/commit", in)
|
||||
return
|
||||
}
|
||||
s.securityFirewallCommitLocal(w, r, in.ChangeID)
|
||||
}
|
||||
func (s *Server) localSecurityFirewallCommit(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
ChangeID string `json:"change_id"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityFirewallCommitLocal(w, r, in.ChangeID)
|
||||
}
|
||||
func (s *Server) securityFirewallCommitLocal(w http.ResponseWriter, r *http.Request, id string) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
if e := s.security.CommitFirewall(id); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, map[string]any{"ok": true, "message": "Firewall change committed."})
|
||||
}
|
||||
|
||||
func (s *Server) securityFirewallRollback(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
ChangeID string `json:"change_id"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPost, "/agent/v1/security/firewall/rollback", in)
|
||||
return
|
||||
}
|
||||
s.securityFirewallRollbackLocal(w, r, in.ChangeID)
|
||||
}
|
||||
func (s *Server) localSecurityFirewallRollback(w http.ResponseWriter, r *http.Request) {
|
||||
var in struct {
|
||||
ChangeID string `json:"change_id"`
|
||||
}
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityFirewallRollbackLocal(w, r, in.ChangeID)
|
||||
}
|
||||
func (s *Server) securityFirewallRollbackLocal(w http.ResponseWriter, r *http.Request, id string) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
if e := s.security.RollbackFirewall(r.Context(), id); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, map[string]any{"ok": true, "message": "Firewall change rolled back."})
|
||||
}
|
||||
|
||||
func (s *Server) securityFail2Ban(w http.ResponseWriter, r *http.Request) {
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodGet, "/agent/v1/security/fail2ban", nil)
|
||||
return
|
||||
}
|
||||
s.localSecurityFail2Ban(w, r)
|
||||
}
|
||||
func (s *Server) localSecurityFail2Ban(w http.ResponseWriter, r *http.Request) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, s.security.Fail2BanPolicy())
|
||||
}
|
||||
func (s *Server) securityApplyFail2Ban(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.Fail2BanPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPut, "/agent/v1/security/fail2ban", in)
|
||||
return
|
||||
}
|
||||
s.securityApplyFail2BanLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) localSecurityApplyFail2Ban(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.Fail2BanPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityApplyFail2BanLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) securityApplyFail2BanLocal(w http.ResponseWriter, r *http.Request, in hostsecurity.Fail2BanPolicy) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.ApplyFail2Ban(r.Context(), in)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
func (s *Server) securityAuditd(w http.ResponseWriter, r *http.Request) {
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodGet, "/agent/v1/security/auditd", nil)
|
||||
return
|
||||
}
|
||||
s.localSecurityAuditd(w, r)
|
||||
}
|
||||
func (s *Server) localSecurityAuditd(w http.ResponseWriter, r *http.Request) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, s.security.AuditdPolicy())
|
||||
}
|
||||
func (s *Server) securityApplyAuditd(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.AuditdPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPut, "/agent/v1/security/auditd", in)
|
||||
return
|
||||
}
|
||||
s.securityApplyAuditdLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) localSecurityApplyAuditd(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.AuditdPolicy
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityApplyAuditdLocal(w, r, in)
|
||||
}
|
||||
func (s *Server) securityApplyAuditdLocal(w http.ResponseWriter, r *http.Request, in hostsecurity.AuditdPolicy) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.ApplyAuditd(r.Context(), in)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
func (s *Server) securityInstall(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.InstallInput
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
component := r.PathValue("component")
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relayWithTimeout(w, r, id, http.MethodPost, "/agent/v1/security/components/"+url.PathEscape(component)+"/install", in, 10*time.Minute)
|
||||
return
|
||||
}
|
||||
s.securityInstallLocal(w, r, component, in)
|
||||
}
|
||||
func (s *Server) localSecurityInstall(w http.ResponseWriter, r *http.Request) {
|
||||
var in hostsecurity.InstallInput
|
||||
if e := read(r, &in); e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
s.securityInstallLocal(w, r, r.PathValue("component"), in)
|
||||
}
|
||||
func (s *Server) securityInstallLocal(w http.ResponseWriter, r *http.Request, component string, in hostsecurity.InstallInput) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.Install(r.Context(), component, in)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
func (s *Server) securityComponentAction(w http.ResponseWriter, r *http.Request) {
|
||||
component, action := r.PathValue("component"), r.PathValue("action")
|
||||
if id := nodeID(r); id > 0 {
|
||||
s.relay(w, r, id, http.MethodPost, "/agent/v1/security/components/"+url.PathEscape(component)+"/actions/"+url.PathEscape(action), map[string]any{})
|
||||
return
|
||||
}
|
||||
s.securityComponentActionLocal(w, r, component, action)
|
||||
}
|
||||
func (s *Server) localSecurityComponentAction(w http.ResponseWriter, r *http.Request) {
|
||||
s.securityComponentActionLocal(w, r, r.PathValue("component"), r.PathValue("action"))
|
||||
}
|
||||
func (s *Server) securityComponentActionLocal(w http.ResponseWriter, r *http.Request, component, action string) {
|
||||
if s.security == nil {
|
||||
http.Error(w, "host security service unavailable", 503)
|
||||
return
|
||||
}
|
||||
v, e := s.security.ComponentAction(r.Context(), component, action)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), 400)
|
||||
return
|
||||
}
|
||||
jsonOut(w, 200, v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user