@@ -0,0 +1,14 @@
|
||||
# READ-ONLY HOST SECURITY AUDIT
|
||||
#
|
||||
# This mode can inspect installed host configuration and files but cannot
|
||||
# change packages or configuration. Active service state is reported only when
|
||||
# the optional host namespace executor is available.
|
||||
services:
|
||||
dockwatch:
|
||||
environment:
|
||||
HOST_ROOT: /host
|
||||
HOST_SECURITY_ENABLED: "true"
|
||||
ALLOW_HOST_SECURITY_CHANGES: "false"
|
||||
ALLOW_HOST_PACKAGE_MANAGEMENT: "false"
|
||||
volumes:
|
||||
- /:/host:ro
|
||||
@@ -0,0 +1,18 @@
|
||||
# FULL HOST SECURITY MANAGEMENT (HIGH PRIVILEGE)
|
||||
#
|
||||
# This override intentionally grants Dockwatch broad host capabilities so it
|
||||
# can install packages, enter the host namespaces, manage services, nftables,
|
||||
# Fail2Ban and auditd. Use only on hosts where Dockwatch is part of your trusted
|
||||
# administration plane. Keep OIDC enabled and restrict admin membership.
|
||||
services:
|
||||
dockwatch:
|
||||
pid: host
|
||||
privileged: true
|
||||
environment:
|
||||
HOST_ROOT: /host
|
||||
HOST_SECURITY_ENABLED: "true"
|
||||
ALLOW_HOST_SECURITY_CHANGES: "true"
|
||||
ALLOW_HOST_PACKAGE_MANAGEMENT: "true"
|
||||
HOST_SECURITY_HOST_PID: "1"
|
||||
volumes:
|
||||
- /:/host:rw
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
package hostsecurity
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderFirewallSafeManagedTable(t *testing.T) {
|
||||
p := FirewallPolicy{
|
||||
Enabled: true, DefaultInbound: "drop", AllowICMP: true,
|
||||
TrustedCIDRs: []string{"10.0.0.0/8", "2001:db8::/32"},
|
||||
Rules: []FirewallRule{{Action: "accept", Protocol: "tcp", Port: "22", Source: "192.0.2.0/24", Comment: "ssh"}, {Action: "drop", Protocol: "udp", Port: "10000-10100"}},
|
||||
}
|
||||
out, err := renderFirewall(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{"table inet dockwatch", "policy drop", "ct state established,related accept", "ip saddr 192.0.2.0/24 tcp dport 22 accept", "udp dport 10000-10100 drop"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("rendered firewall missing %q:\n%s", want, out)
|
||||
}
|
||||
}
|
||||
if strings.Contains(out, "flush ruleset") {
|
||||
t.Fatal("managed firewall must never flush the global ruleset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderFirewallRejectsUnsafeInput(t *testing.T) {
|
||||
cases := []FirewallPolicy{
|
||||
{DefaultInbound: "drop", Rules: []FirewallRule{{Action: "accept", Protocol: "tcp", Port: "0"}}},
|
||||
{DefaultInbound: "drop", Rules: []FirewallRule{{Action: "accept", Protocol: "tcp", Port: "22", Source: "not-a-cidr"}}},
|
||||
{DefaultInbound: "drop", Rules: []FirewallRule{{Action: "accept", Protocol: "tcp", Port: "22", Comment: "x\nadd rule"}}},
|
||||
}
|
||||
for i, p := range cases {
|
||||
if _, err := renderFirewall(p); err == nil {
|
||||
t.Fatalf("case %d should fail", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderFail2Ban(t *testing.T) {
|
||||
p := Fail2BanPolicy{Bantime: "1h", Findtime: "10m", MaxRetry: 5, Backend: "auto", IgnoreIP: []string{"127.0.0.1/8", "::1"}, Jails: []Fail2BanJail{{Name: "sshd", Enabled: true, Port: "ssh", Filter: "sshd", Backend: "systemd"}}}
|
||||
out, err := renderFail2Ban(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, want := range []string{"[DEFAULT]", "bantime = 1h", "[sshd]", "enabled = true", "backend = systemd"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("missing %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderAuditdUsesFileWatchesOnly(t *testing.T) {
|
||||
p := AuditdPolicy{IdentityFiles: true, Docker: true, Custom: []AuditWatch{{Path: "/srv/app", Permissions: "wa", Key: "app-config"}}}
|
||||
out, err := renderAuditd(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(out, "-w /etc/passwd -p wa -k identity") || !strings.Contains(out, "-w /srv/app -p wa -k app-config") {
|
||||
t.Fatalf("unexpected rules:\n%s", out)
|
||||
}
|
||||
if strings.Contains(out, "-a always") {
|
||||
t.Fatal("Dockwatch preset should not inject broad syscall rules")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuditWatchValidation(t *testing.T) {
|
||||
bad := []AuditdPolicy{{Custom: []AuditWatch{{Path: "relative", Permissions: "wa", Key: "x"}}}, {Custom: []AuditWatch{{Path: "/srv/x", Permissions: "777", Key: "x"}}}, {Custom: []AuditWatch{{Path: "/srv/x", Permissions: "wa", Key: "x;bad"}}}}
|
||||
for i, p := range bad {
|
||||
if _, err := renderAuditd(p); err == nil {
|
||||
t.Fatalf("case %d should fail", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user