# ============================================================================ # 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: # # # ... # Include /etc/apache2/conf-available/lupmis-hardening.conf # # # 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, …) # --------------------------------------------------------------------------- # is server-config only, which is exactly the context this # file is meant for, and it covers the whole path — so unlike it # does stop /.git/config. # # .well-known is exempt on purpose: blocking it breaks Let's Encrypt (ACME) # certificate issuance and renewal. Require all denied Require all denied # 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. RewriteEngine On RewriteCond %{REQUEST_URI} !^/\.well-known/ RewriteRule (^|/)\. - [R=404,L] # --------------------------------------------------------------------------- # 2. Files that should never be served, if one lands in the document root # --------------------------------------------------------------------------- Require all denied Require all denied # --------------------------------------------------------------------------- # 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 . # # 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; }