Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 1 addition & 242 deletions e2e/localdns/validate-localdns-exporter-metrics.sh
Original file line number Diff line number Diff line change
Expand Up @@ -364,245 +364,4 @@ INSTANCE_NAME=$(echo "$ACTIVE_INSTANCES" | head -n 1)
echo " ✓ Found instance: $INSTANCE_NAME"
echo ""

# Step 13: Verify all 16 systemd security directives via systemctl show on the live instance.
# This checks the runtime-effective values — what systemd actually enforces — not just what's
# in the unit file. This catches drop-in overrides, syntax errors, and unsupported directives.
echo "13. Verifying all 16 systemd security directives on live instance..."
echo ""

SECURITY_PROPS_1=$(systemctl show "$INSTANCE_NAME" \
--property=DynamicUser,PrivateTmp,ProtectSystem,ProtectHome,ReadOnlyPaths,NoNewPrivileges \
2>/dev/null || true)
SECURITY_PROPS_2=$(systemctl show "$INSTANCE_NAME" \
--property=ProtectKernelTunables,ProtectKernelModules,ProtectControlGroups,RestrictAddressFamilies \
2>/dev/null || true)
SECURITY_PROPS_3=$(systemctl show "$INSTANCE_NAME" \
--property=RestrictNamespaces,LockPersonality,RestrictRealtime,RestrictSUIDSGID,RemoveIPC,PrivateMounts \
2>/dev/null || true)

SECURITY_PROPS="$SECURITY_PROPS_1
$SECURITY_PROPS_2
$SECURITY_PROPS_3"

echo " Retrieved security properties:"
echo "$SECURITY_PROPS" | sed 's/^/ /'
echo ""

FAILED_CHECKS=0

if ! echo "$SECURITY_PROPS" | grep -q "^DynamicUser=yes$"; then
echo " ❌ ERROR: DynamicUser not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ DynamicUser=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^PrivateTmp=yes$"; then
echo " ❌ ERROR: PrivateTmp not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ PrivateTmp=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^ProtectSystem=strict$"; then
echo " ❌ ERROR: ProtectSystem not strict"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ProtectSystem=strict"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^ProtectHome=yes$"; then
echo " ❌ ERROR: ProtectHome not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ProtectHome=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -qE "^ReadOnlyPaths=/$|^ReadOnlyPaths=/ "; then
echo " ❌ ERROR: ReadOnlyPaths not set to /"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ReadOnlyPaths=/"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^NoNewPrivileges=yes$"; then
echo " ❌ ERROR: NoNewPrivileges not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ NoNewPrivileges=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^ProtectKernelTunables=yes$"; then
echo " ❌ ERROR: ProtectKernelTunables not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ProtectKernelTunables=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^ProtectKernelModules=yes$"; then
echo " ❌ ERROR: ProtectKernelModules not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ProtectKernelModules=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^ProtectControlGroups=yes$"; then
echo " ❌ ERROR: ProtectControlGroups not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ ProtectControlGroups=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -qE "RestrictAddressFamilies=.*AF_UNIX"; then
echo " ❌ ERROR: RestrictAddressFamilies does not include AF_UNIX"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
# AF_INET and AF_INET6 are expected alongside AF_UNIX for socket-activation compatibility
# with future systemd versions (seccomp filter must allow the inherited socket's address family)
if ! echo "$SECURITY_PROPS" | grep -qE "RestrictAddressFamilies=.*AF_INET"; then
echo " ❌ ERROR: RestrictAddressFamilies does not include AF_INET (required for socket activation)"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6"
fi
fi

if ! echo "$SECURITY_PROPS" | grep -q "^RestrictNamespaces=yes$"; then
echo " ❌ ERROR: RestrictNamespaces not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ RestrictNamespaces=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^LockPersonality=yes$"; then
echo " ❌ ERROR: LockPersonality not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ LockPersonality=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^RestrictRealtime=yes$"; then
echo " ❌ ERROR: RestrictRealtime not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ RestrictRealtime=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^RestrictSUIDSGID=yes$"; then
echo " ❌ ERROR: RestrictSUIDSGID not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ RestrictSUIDSGID=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^RemoveIPC=yes$"; then
echo " ❌ ERROR: RemoveIPC not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ RemoveIPC=yes"
fi

if ! echo "$SECURITY_PROPS" | grep -q "^PrivateMounts=yes$"; then
echo " ❌ ERROR: PrivateMounts not enabled"
FAILED_CHECKS=$((FAILED_CHECKS + 1))
else
echo " ✓ PrivateMounts=yes"
fi

echo ""
if [ "$FAILED_CHECKS" -gt 0 ]; then
echo "=== ❌ Security Configuration Validation FAILED ==="
echo "$FAILED_CHECKS out of 16 security directives are not properly configured"
exit 1
fi
echo "✓ All 16 security directives are properly configured"
echo ""

# Step 14: Get PID of the instance for runtime enforcement checks
echo "14. Getting PID of instance..."
INSTANCE_PID=$(systemctl show "$INSTANCE_NAME" --property=MainPID --value 2>/dev/null || echo "0")

if [ "$INSTANCE_PID" = "0" ] || [ -z "$INSTANCE_PID" ]; then
echo " ⚠️ Instance PID not found, skipping process-level checks"
else
echo " ✓ Instance PID: $INSTANCE_PID"
echo ""

# Step 15: Verify not running as root (DynamicUser runtime enforcement)
echo "15. Verifying DynamicUser runtime enforcement (not running as root)..."
INSTANCE_USER=$(ps -o user= -p "$INSTANCE_PID" 2>/dev/null || echo "unknown")
if [ "$INSTANCE_USER" = "root" ]; then
echo " ❌ ERROR: Instance running as root (DynamicUser not enforced at runtime)"
exit 1
fi
echo " ✓ Running as dynamic user: $INSTANCE_USER"
echo ""

# Step 16: Verify no network sockets (RestrictAddressFamilies runtime enforcement)
echo "16. Verifying RestrictAddressFamilies runtime enforcement (no network sockets)..."

# Use /proc filesystem for portability (works on all distros without lsof)
# Note: Socket-activated services inherit the accepted connection as stdin/stdout (fd 0/1).
# This inherited socket is AF_INET but is expected and allowed. We only care about
# NEW sockets the service creates, not the inherited activation socket.
NETWORK_SOCKETS=0
INHERITED_SOCKET_INODE=""

if [ -d "/proc/$INSTANCE_PID/fd" ]; then
# Find the stdin socket inode (the inherited activation socket)
if [ -L "/proc/$INSTANCE_PID/fd/0" ]; then
STDIN_TARGET=$(readlink "/proc/$INSTANCE_PID/fd/0" 2>/dev/null || echo "")
INHERITED_SOCKET_INODE=$(echo "$STDIN_TARGET" | sed -n 's/^socket:\[\([0-9]*\)\]$/\1/p')
fi

# Iterate through file descriptors to find sockets
for fd in /proc/"$INSTANCE_PID"/fd/*; do
if [ -L "$fd" ]; then
FD_TARGET=$(readlink "$fd" 2>/dev/null || echo "")
SOCKET_INODE=$(echo "$FD_TARGET" | sed -n 's/^socket:\[\([0-9]*\)\]$/\1/p')
if [ -n "$SOCKET_INODE" ]; then

# Skip the inherited stdin/stdout socket from socket activation
if [ -n "$INHERITED_SOCKET_INODE" ] && [ "$SOCKET_INODE" = "$INHERITED_SOCKET_INODE" ]; then
continue
fi

# Use ss to check if this socket is TCP/UDP (network socket)
if ss -tupn 2>/dev/null | grep -q "inode:$SOCKET_INODE"; then
NETWORK_SOCKETS=$((NETWORK_SOCKETS + 1))
echo " Found unexpected network socket: inode=$SOCKET_INODE"
fi
fi
fi
done
else
echo " ⚠️ WARNING: Cannot access /proc/$INSTANCE_PID/fd, skipping socket inspection"
fi

if [ "$NETWORK_SOCKETS" != "0" ]; then
echo " ❌ ERROR: Instance has $NETWORK_SOCKETS unexpected network socket(s) (RestrictAddressFamilies not enforced)"
exit 1
fi
echo " ✓ No unexpected network sockets (AF_UNIX only, restriction enforced)"
echo ""

# Step 17: Verify namespace isolation (RestrictNamespaces runtime enforcement)
echo "17. Verifying namespace isolation..."
if [ -d "/proc/$INSTANCE_PID/ns" ]; then
NS_COUNT=$(find /proc/"$INSTANCE_PID"/ns/ -mindepth 1 -maxdepth 1 2>/dev/null | wc -l)
if [ "$NS_COUNT" -lt 5 ]; then
echo " ⚠️ WARNING: Only $NS_COUNT namespaces (expected 5+ for proper isolation)"
else
echo " ✓ Process has $NS_COUNT namespaces (properly isolated)"
fi
else
echo " ⚠️ Cannot verify namespaces (proc not accessible)"
fi
echo ""
fi

echo "=== ✓ Security Hardening Validation Passed ==="
echo "Configuration: All 16 systemd security directives verified on live instance"
if [ -n "${INSTANCE_PID:-}" ] && [ "${INSTANCE_PID:-0}" != "0" ]; then
echo "Runtime: DynamicUser, RestrictAddressFamilies, and namespace isolation enforced"
fi
echo "=== ✓ All localdns exporter functional validations passed ==="
Comment thread
jingwenw15 marked this conversation as resolved.
Outdated
84 changes: 84 additions & 0 deletions spec/parts/linux/cloud-init/artifacts/localdns_exporter_spec.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,89 @@
#!/bin/bash

Describe 'localdns-exporter@.service security hardening'
UNIT_FILE="./parts/linux/cloud-init/artifacts/localdns-exporter@.service"

It 'should have DynamicUser=yes'
When run grep -q "^DynamicUser=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have PrivateTmp=yes'
When run grep -q "^PrivateTmp=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have ProtectSystem=strict'
When run grep -q "^ProtectSystem=strict$" "$UNIT_FILE"
The status should be success
End

It 'should have ProtectHome=yes'
When run grep -q "^ProtectHome=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have ReadOnlyPaths=/'
When run grep -q "^ReadOnlyPaths=/$" "$UNIT_FILE"
The status should be success
End

It 'should have NoNewPrivileges=yes'
When run grep -q "^NoNewPrivileges=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have ProtectKernelTunables=yes'
When run grep -q "^ProtectKernelTunables=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have ProtectKernelModules=yes'
When run grep -q "^ProtectKernelModules=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have ProtectControlGroups=yes'
When run grep -q "^ProtectControlGroups=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have RestrictAddressFamilies with AF_UNIX AF_INET AF_INET6'
When run grep -q "^RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6$" "$UNIT_FILE"
The status should be success
End

It 'should have RestrictNamespaces=yes'
When run grep -q "^RestrictNamespaces=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have LockPersonality=yes'
When run grep -q "^LockPersonality=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have RestrictRealtime=yes'
When run grep -q "^RestrictRealtime=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have RestrictSUIDSGID=yes'
When run grep -q "^RestrictSUIDSGID=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have RemoveIPC=yes'
When run grep -q "^RemoveIPC=yes$" "$UNIT_FILE"
The status should be success
End

It 'should have PrivateMounts=yes'
When run grep -q "^PrivateMounts=yes$" "$UNIT_FILE"
The status should be success
End
End

Describe 'localdns_exporter.sh HTTP request routing'
SCRIPT_PATH="./parts/linux/cloud-init/artifacts/localdns_exporter.sh"

Expand Down
Loading