# ============================================================================
# LUPMIS2 — Apache hardening against source-code and secret disclosure
# ============================================================================
#
# WHY THIS EXISTS
# ---------------
# This is preventive hardening. No LUPMIS host is known to have been
# compromised — these rules are here so that the most common automated attack
# against a PHP deployment cannot succeed.
#
# Scanners continuously probe every public host for two things:
#
#     /.env            — application secrets: database passwords, API keys
#     /.git/…          — the repository, if a working copy was deployed
#
# The second is the more damaging of the two. Given /.git/index together with
# /.git/objects, an attacker can reconstruct the entire repository: every file
# and every past commit. That means any credential ever committed is exposed,
# even if it has since been changed, because it remains in the history.
#
# That last point is directly relevant to LUPMIS. The minio-uploads
# integration has had access keys written into upload.php, download.php and
# get_file_url.php, and those files are tracked in Gitea — so the keys are in
# the history regardless of later rotation. The exposure is only theoretical
# while that repository is private and its .git directory is not web-served;
# these rules keep the second half of that sentence true.
#
# WHICH HOSTS
# -----------
# Apply to EVERY vhost. The risk is not equal across them:
#
#   • The PWA is not currently exposed — its document root is dist/, which
#     contains neither .env nor .git.
#   • Any app deployed by copying a working directory (a git clone) into the
#     document root is exposed by construction. The minio-uploads project, for
#     example, carries a .git directory and composer files.
#
# Checking is quick, from outside the network:
#     curl -sS -o /dev/null -w '%{http_code}\n' https://HOST/.env
#     curl -sS -o /dev/null -w '%{http_code}\n' https://HOST/.git/config
# Anything other than 403 or 404 needs attention.
#
# HOW TO USE
# ----------
# Preferred — in the vhost or server config, where it cannot be disabled by a
# stray AllowOverride and applies to the whole tree:
#
#     <VirtualHost *:443>
#         ...
#         Include /etc/apache2/conf-available/lupmis-hardening.conf
#     </VirtualHost>
#
# Then:  a2enconf lupmis-hardening && apachectl configtest && systemctl reload apache2
#
# If you can only use .htaccess, the equivalent rules are already in the PWA's
# public/.htaccess — but note .htaccess needs AllowOverride to be enabled, so
# the vhost form is the safer of the two.
#
# THIS IS DEFENCE IN DEPTH, NOT THE FIX
# -------------------------------------
# The real fix is that .git and .env must not be inside a document root at all.
# Deploy build output (or `git archive`), not a working copy, and keep secrets
# in environment variables outside the served tree. These rules are what
# protects you when that goes wrong again.
# ============================================================================


# ---------------------------------------------------------------------------
# 1. Hidden directories and files  (.git, .env, .svn, .hg, .DS_Store, …)
# ---------------------------------------------------------------------------
# <DirectoryMatch> is server-config only, which is exactly the context this
# file is meant for, and it covers the whole path — so unlike <FilesMatch> it
# does stop /.git/config.
#
# .well-known is exempt on purpose: blocking it breaks Let's Encrypt (ACME)
# certificate issuance and renewal.

<DirectoryMatch "/\.(?!well-known)">
    Require all denied
</DirectoryMatch>

<FilesMatch "^\.">
    Require all denied
</FilesMatch>

# mod_rewrite equivalent — also covers odd forms such as /./.env, which the
# captured log shows being used, and returns 404 rather than 403 so a probe
# learns nothing about what is present.
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !^/\.well-known/
    RewriteRule (^|/)\. - [R=404,L]
</IfModule>


# ---------------------------------------------------------------------------
# 2. Files that should never be served, if one lands in the document root
# ---------------------------------------------------------------------------
<FilesMatch "\.(sql|sqlite|sqlite3|db|bak|backup|old|orig|save|swp|swo|log|ini|conf|key|pem|crt|p12|pfx|yml|yaml)$">
    Require all denied
</FilesMatch>

<FilesMatch "^(composer\.(json|lock)|package(-lock)?\.json|yarn\.lock|Dockerfile|docker-compose\.ya?ml|Makefile|README\.md|\.htpasswd)$">
    Require all denied
</FilesMatch>


# ---------------------------------------------------------------------------
# 3. Do not advertise the software version, and do not list directories
# ---------------------------------------------------------------------------
# ServerTokens / ServerSignature are global directives — put them in the main
# server config, not inside a <VirtualHost>.
#
#   ServerTokens Prod
#   ServerSignature Off

Options -Indexes


# ---------------------------------------------------------------------------
# 4. Proxy — make the logs usable
# ---------------------------------------------------------------------------
# Where Apache sits behind the openresty reverse proxy, it logs the proxy's
# Docker-internal address rather than the real caller. If that is the case
# here, an access log cannot tell you who probed the site, and the traffic
# cannot be blocked or reported. Have the proxy pass the caller through:
#
#     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#     proxy_set_header X-Real-IP       $remote_addr;
#
# and have Apache trust it, so %a in the log is the real address:
#
#     RemoteIPHeader X-Forwarded-For
#     RemoteIPInternalProxy 172.20.0.0/16
#     LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
#
# (RemoteIPHeader needs mod_remoteip: a2enmod remoteip)
#
# Blocking at the proxy as well as here is worthwhile — it stops the request
# before it reaches the application at all:
#
#     location ~ /\.(?!well-known) { return 404; }
