Add src/csp-perms-audit.sh
This commit is contained in:
parent
da76544520
commit
bbb24b77a6
|
@ -0,0 +1,105 @@
|
||||||
|
# Function to audit CSP, Permissions Policy, and secure headers for a given URL.
|
||||||
|
csp_perms_audit() {
|
||||||
|
URL=$1
|
||||||
|
if [ -z "$URL" ]; then
|
||||||
|
echo "Usage: csp_perms_audit <URL>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate a nonce for CSP policies
|
||||||
|
NONCE=$(openssl rand -base64 12)
|
||||||
|
|
||||||
|
# Instructions and example Traefik labels for setting CSP, Permissions Policy, and secure headers.
|
||||||
|
echo "=== Instructions ==="
|
||||||
|
echo "Use the following analysis to create a Traefik label-based Content-Security-Policy (CSP), Permissions Policy, and a set of secure headers:"
|
||||||
|
echo "- This CSP includes a dynamically generated nonce ($NONCE). Apply the same nonce to each inline script to allow it to execute."
|
||||||
|
echo "- 'unsafe-inline' is added to support older browsers that do not recognize nonces or hashes. Modern browsers will ignore it if a nonce or hash is present."
|
||||||
|
echo "- If you prefer hashes instead of nonces, precompute SHA-256 hashes for each inline script's content."
|
||||||
|
echo "- Remember: Traefik labels do not support multiline YAML syntax, so ensure the CSP, Permissions Policy, and secure headers are formatted as single-line strings."
|
||||||
|
|
||||||
|
echo "\nImportant: Each service should have a unique `secure-headers` middleware to avoid conflicts. Use a specific name for each service's `secure-headers` middleware, like `serviceA-secure-headers`, to ensure settings are applied uniquely to that service."
|
||||||
|
|
||||||
|
echo "\nExample Traefik labels for your Docker Compose or Traefik configuration:\n"
|
||||||
|
|
||||||
|
echo "# Content-Security-Policy (CSP) Label"
|
||||||
|
echo "traefik.http.middlewares.csp-headers.headers.customResponseHeaders.Content-Security-Policy: \"default-src 'self'; script-src 'self' 'strict-dynamic' 'nonce-$NONCE' 'unsafe-inline'; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self' https://use.typekit.net; frame-src 'none'; base-uri 'none'; object-src 'none'; require-trusted-types-for 'script';\""
|
||||||
|
|
||||||
|
echo "\n# Permissions-Policy Label"
|
||||||
|
echo "traefik.http.middlewares.permissions-policy.headers.customResponseHeaders.Permissions-Policy: \"geolocation=(), microphone=(), camera=(), payment=(), usb=()\""
|
||||||
|
|
||||||
|
echo "\n# Additional Secure Headers (ensure middleware is unique for each service)"
|
||||||
|
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-Frame-Options: \"DENY\""
|
||||||
|
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-Content-Type-Options: \"nosniff\""
|
||||||
|
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.X-XSS-Protection: \"1; mode=block\""
|
||||||
|
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.Referrer-Policy: \"no-referrer\""
|
||||||
|
echo "traefik.http.middlewares.unique-secure-headers.headers.customResponseHeaders.Strict-Transport-Security: \"max-age=63072000; includeSubDomains; preload\""
|
||||||
|
|
||||||
|
echo "\n=== Checking Headers for Permissions and Relevant Policies on $URL ==="
|
||||||
|
|
||||||
|
# Fetch headers and parse for permissions policies
|
||||||
|
curl -s -D - "$URL" -o /dev/null | grep -i "feature-policy\|permissions-policy" || echo "No relevant headers found."
|
||||||
|
|
||||||
|
# Extract embedded resources (scripts, stylesheets, images, frames)
|
||||||
|
echo "\n=== Embedded Resources (Scripts, Stylesheets, Images, Frames) ==="
|
||||||
|
embedded_resources=$(curl -s "$URL" | grep -Eo 'src="([^"]+)"|href="([^"]+)|url\(([^)]+)\)' | sed -E 's/src=|href=|url\(|\)|"//g')
|
||||||
|
for resource in $embedded_resources; do
|
||||||
|
echo "$resource"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Checking accessibility of external resources
|
||||||
|
echo "\n=== Checking External Resources Accessibility ==="
|
||||||
|
check_resource_accessibility() {
|
||||||
|
resource=$1
|
||||||
|
# Perform a HEAD request and check for 200, 301, 302, 307, 308 status codes
|
||||||
|
if curl -s -o /dev/null -w "%{http_code}" --head "$resource" | grep -E "^(200|301|302|307|308)$" > /dev/null; then
|
||||||
|
echo "✔️ Accessible: $resource"
|
||||||
|
else
|
||||||
|
echo "❌ Inaccessible: $resource (This may break functionality)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Recursive extraction for linked CSS files to capture additional resources
|
||||||
|
for css_url in $(echo "$embedded_resources" | grep -E "\.css$"); do
|
||||||
|
css_resources=$(curl -s "$css_url" | grep -Eo 'url\(([^)]+)\)' | sed -E 's/url\(|\)|"//g')
|
||||||
|
for css_resource in $css_resources; do
|
||||||
|
echo "$css_resource (from $css_url)"
|
||||||
|
external_resources="$external_resources $css_resource"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
external_resources=$(echo "$embedded_resources" | grep -E "^https?://")
|
||||||
|
for resource in $external_resources; do
|
||||||
|
check_resource_accessibility "$resource"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check for inline scripts
|
||||||
|
echo "\n=== Checking for Inline Scripts ==="
|
||||||
|
inline_scripts=$(curl -s "$URL" | grep -i "<script>" | wc -l)
|
||||||
|
if [ "$inline_scripts" -gt 0 ]; then
|
||||||
|
echo "⚠️ Inline scripts detected. Consider securing them with nonces or hashes."
|
||||||
|
else
|
||||||
|
echo "✔️ No inline scripts detected (or already secured with nonces)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for embedded iFrames
|
||||||
|
echo "\n=== Checking for Embedded iFrames ==="
|
||||||
|
iframes=$(curl -s "$URL" | grep -i "<iframe" | wc -l)
|
||||||
|
if [ "$iframes" -gt 0 ]; then
|
||||||
|
echo "⚠️ iFrames detected. Ensure 'X-Frame-Options: DENY' or 'frame-src' CSP is properly configured."
|
||||||
|
else
|
||||||
|
echo "✔️ No iFrames detected (or no conflict with 'X-Frame-Options: DENY')."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for mixed content
|
||||||
|
echo "\n=== Checking for Mixed Content ==="
|
||||||
|
mixed_content=$(curl -s "$URL" | grep -E "http://" | wc -l)
|
||||||
|
if [ "$mixed_content" -gt 0 ]; then
|
||||||
|
echo "⚠️ Mixed content detected. Ensure all resources are loaded over HTTPS."
|
||||||
|
else
|
||||||
|
echo "✔️ No mixed content detected."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Suggested default CSP to allow 'self' hosted assets
|
||||||
|
echo "\n=== Suggested CSP for 'self'-hosted Assets ==="
|
||||||
|
echo "default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com; img-src 'self' data:; connect-src 'self'; font-src 'self' https://use.typekit.net; frame-src 'none'; base-uri 'none'; object-src 'none'; require-trusted-types-for 'script';"
|
||||||
|
}
|
Loading…
Reference in New Issue