mb-admin/scripts/pma-gateway/create_pma_gateway.sh

324 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/bin/bash
# ==============================================================================
# Script: create_pma_gateway.sh
# Purpose: Create a time-limited gateway URL for phpMyAdmin on Virtuozzo LLSMP.
# Uses Let's Encrypt cert in the dedicated phpMyAdmin vhost (port 8443).
# Dynamically detects Let's Encrypt certificate paths.
# Usage: create_pma_gateway.sh --validity=30 [--slug=myalias]
# Outputs: Prints the generated URL.
# ==============================================================================
set -euo pipefail
SLUG=""
VALIDITY=30 # minutes
for arg in "$@"; do
case $arg in
--slug=*) SLUG="${arg#*=}" ;;
--validity=*) VALIDITY="${arg#*=}" ;;
*) echo "ERROR: Unknown argument $arg"; exit 1 ;;
esac
done
if [[ -z "$SLUG" ]]; then
SLUG=$(openssl rand -hex 4) # 8-char random
fi
# Determine environment public host (no node prefix) - used for certificate lookup and URL
if [[ -n "${JELASTIC_ENV_DOMAIN:-}" ]]; then
ENV_HOST="$JELASTIC_ENV_DOMAIN"
else
ENV_HOST=$(hostname -f)
ENV_HOST=${ENV_HOST#node*-} # strip nodeXXXX-
fi
PMADB_DIR="/usr/share/phpMyAdmin"
GATEWAY_FILE="$PMADB_DIR/access-db-$SLUG.php"
SECRET_FILE="/var/lib/jelastic/keys/mbadmin_secret"
sudo mkdir -p "$(dirname $SECRET_FILE)"
if [[ ! -f "$SECRET_FILE" ]]; then
sudo sh -c "openssl rand -hex 32 > $SECRET_FILE"
fi
sudo chown litespeed:litespeed "$SECRET_FILE"
sudo chmod 644 "$SECRET_FILE"
SECRET=$(sudo cat "$SECRET_FILE" | xargs)
now=$(date +%s)
expires=$((now + VALIDITY*60))
# token = base64("$SLUG:$expires") . '.' . HMAC_SHA256(secret, data)
data="$SLUG:$expires"
base=$(printf "%s" "$data" | base64 | tr -d '\n')
mac=$(php -r "echo hash_hmac('sha256', '$data', '$SECRET');")
token="$base.$mac"
# Secure the phpMyAdmin vhost with Rewrite Rules to block direct access
VHOST_CONFIG="/usr/share/phpMyAdmin/vhost.conf"
NEEDS_RESTART=0
# --- MODIFICATION: Dynamically determine Let's Encrypt Cert Paths ---
LE_LIVE_DIR="/etc/letsencrypt/live"
LE_CERT_DIR=""
# Attempt to find the directory matching ENV_HOST
if [[ -d "$LE_LIVE_DIR/$ENV_HOST" ]]; then
LE_CERT_DIR="$LE_LIVE_DIR/$ENV_HOST"
echo "INFO: Found Let's Encrypt cert directory matching ENV_HOST: $LE_CERT_DIR" >&2
else
# If not found, iterate through subdirectories in $LE_LIVE_DIR to find a suitable one
# This is a fallback, assuming there might be only one relevant cert directory.
# Or, you could add more logic here to match patterns if needed.
for dir in "$LE_LIVE_DIR"/*/; do
if [[ -d "$dir" ]]; then
candidate_dir=$(basename "$dir")
echo "INFO: Checking Let's Encrypt cert directory: $candidate_dir" >&2
# Add more specific checks here if multiple domains exist and you need to pick the right one.
# For now, just pick the first one that has the required files.
if [[ -f "$dir/privkey.pem" ]] && [[ -f "$dir/fullchain.pem" ]]; then
LE_CERT_DIR="$dir"
echo "INFO: Found Let's Encrypt cert directory with required files: $LE_CERT_DIR" >&2
break
fi
fi
done
fi
# Set the final key and cert file paths based on the found directory
if [[ -n "$LE_CERT_DIR" ]]; then
LE_KEY_FILE="$LE_CERT_DIR/privkey.pem"
LE_CERT_FILE="$LE_CERT_DIR/fullchain.pem"
else
echo "FATAL: Let's Encrypt certificate directory could not be found in $LE_LIVE_DIR for ENV_HOST: $ENV_HOST" >&2
echo " Checked specific path: $LE_LIVE_DIR/$ENV_HOST" >&2
echo " Checked other subdirectories for privkey.pem and fullchain.pem." >&2
exit 1
fi
# Check if the Let's Encrypt files exist at the determined paths
if [[ ! -f "$LE_KEY_FILE" ]] || [[ ! -f "$LE_CERT_FILE" ]]; then
echo "FATAL: Let's Encrypt certificate files not found at determined paths:" >&2
echo " Key: $LE_KEY_FILE" >&2
echo " Cert: $LE_CERT_FILE" >&2
exit 1
fi
echo "INFO: Using Let's Encrypt certificate paths:" >&2
echo " Key: $LE_KEY_FILE" >&2
echo " Cert: $LE_CERT_FILE" >&2
# If vhost config is missing or empty, recreate it from a known-good default WITH Let's Encrypt Certs.
if [ ! -s "$VHOST_CONFIG" ]; then
echo "Warning: $VHOST_CONFIG is empty or missing. Recreating from default with Let's Encrypt certs." >&2
sudo tee "$VHOST_CONFIG" > /dev/null <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<virtualHostConfig>
<docRoot>/usr/share/phpMyAdmin/</docRoot>
<enableGzip>1</enableGzip>
<logging>
<log>
<useServer>0</useServer>
<fileName>\$SERVER_ROOT/logs/error.log</fileName>
<logLevel>DEBUG</logLevel>
<rollingSize>10M</rollingSize>
</log>
<accessLog>
<useServer>0</useServer>
<fileName>\$SERVER_ROOT/logs/access.log</fileName>
<rollingSize>10M</rollingSize>
<keepDays>30</keepDays>
<compressArchive>0</compressArchive>
</accessLog>
</logging>
<index>
<useServer>0</useServer>
<indexFiles>index.php, index.html</indexFiles>
<autoIndex>0</autoIndex>
<autoIndexURI>/_autoindex/default.php</autoIndexURI>
</index>
<customErrorPages>
<errorPage>
<errCode>404</errCode>
<url>/error404.html</url>
</errorPage>
</customErrorPages>
<htAccess>
<allowOverride>31</allowOverride>
<accessFileName>.htaccess</accessFileName>
</htAccess>
<expires>
<enableExpires>1</enableExpires>
</expires>
<security>
<wpProtectAction>0</wpProtectAction>
<hotlinkCtrl>
<enableHotlinkCtrl>0</enableHotlinkCtrl>
<suffixes>gif, jpeg, jpg</suffixes>
<allowDirectAccess>1</allowDirectAccess>
<onlySelf>1</onlySelf>
</hotlinkCtrl>
<accessControl>
<allow>*</allow>
</accessControl>
<wpProtectLimit>10</wpProtectLimit></security>
<cache>
<storage>
<cacheStorePath>/tmp/lscache/vhosts/\$VH_NAME</cacheStorePath>
</storage>
</cache>
<rewrite>
<enable>0</enable>
<logLevel>0</logLevel>
<rules>RewriteCond %{HTTP_USER_AGENT} ^NameOfBadRobot
RewriteRule ^/nospider/ - [F]</rules>
</rewrite>
<vhssl>
<keyFile>$LE_KEY_FILE</keyFile>
<certFile>$LE_CERT_FILE</certFile>
<certChain>1</certChain>
</vhssl>
<frontPage>
<enable>0</enable>
<disableAdmin>0</disableAdmin>
</frontPage>
<awstats>
<updateMode>0</updateMode>
<workingDir>\$VH_ROOT/awstats</workingDir>
<awstatsURI>/awstats/</awstatsURI>
<siteDomain>localhost</siteDomain>
<siteAliases>127.0.0.1 localhost</siteAliases>
<updateInterval>86400</updateInterval>
<updateOffset>0</updateOffset>
</awstats>
</virtualHostConfig>
EOF
NEEDS_RESTART=1
fi
if [ -f "$VHOST_CONFIG" ]; then
MARKER="# PMA Gateway Security Rules"
# If rules are not already in place, add them.
if ! sudo grep -qF "$MARKER" "$VHOST_CONFIG"; then
# Ensure xmlstarlet is installed, as it's the safest way to edit XML.
if ! command -v xmlstarlet &> /dev/null; then
echo "xmlstarlet not found. Installing for safe XML editing..." >&2
if ! sudo dnf install -y xmlstarlet; then
echo "FATAL: Failed to install xmlstarlet. Cannot safely modify vhost." >&2
exit 1
fi
fi
# Define the new rules content. Note the lack of indentation.
# xmlstarlet will handle the formatting.
NEW_RULES_CONTENT=$(cat <<'EOF'
# PMA Gateway Security Rules
# Allow access to the gateway scripts themselves
RewriteCond %{REQUEST_URI} ^/access-db-.*\.php$
RewriteRule .* - [L]
# For all other requests, block if the security cookie is not present
RewriteCond %{HTTP_COOKIE} !pma_access_granted
RewriteRule .* - [F,L]
EOF
)
# Use xmlstarlet to atomically update the rewrite block in-place.
# This is far safer than sed/awk for structured XML.
if ! sudo xmlstarlet ed -L \
-u "//virtualHostConfig/rewrite/enable" -v "1" \
-u "//virtualHostConfig/rewrite/rules" -v "$NEW_RULES_CONTENT" \
"$VHOST_CONFIG"; then
echo "FATAL: xmlstarlet failed to update $VHOST_CONFIG." >&2
exit 1
fi
NEEDS_RESTART=1
fi
# --- MODIFICATION: Update SSL Certs in Existing vhost.conf if paths differ ---
# Check if the current SSL paths in vhost.conf match the desired Let's Encrypt paths
CURRENT_KEY_PATH=$(sudo xmlstarlet sel -t -v "//virtualHostConfig/vhssl/keyFile" "$VHOST_CONFIG" 2>/dev/null)
CURRENT_CERT_PATH=$(sudo xmlstarlet sel -t -v "//virtualHostConfig/vhssl/certFile" "$VHOST_CONFIG" 2>/dev/null)
if [[ "$CURRENT_KEY_PATH" != "$LE_KEY_FILE" ]] || [[ "$CURRENT_CERT_PATH" != "$LE_CERT_FILE" ]]; then
echo "Updating SSL certificate paths in $VHOST_CONFIG to Let's Encrypt certs..." >&2
if ! sudo xmlstarlet ed -L \
-u "//virtualHostConfig/vhssl/keyFile" -v "$LE_KEY_FILE" \
-u "//virtualHostConfig/vhssl/certFile" -v "$LE_CERT_FILE" \
"$VHOST_CONFIG"; then
echo "FATAL: xmlstarlet failed to update SSL paths in $VHOST_CONFIG." >&2
exit 1
fi
NEEDS_RESTART=1
fi
else
echo "Warning: phpMyAdmin vhost config not found at $VHOST_CONFIG. Cannot apply security rules or update SSL." >&2
fi
sudo tee "$GATEWAY_FILE" >/dev/null <<'PHP'
<?php
// Secure phpMyAdmin gateway auto-generated, do NOT edit manually.
ini_set('session.cookie_httponly', 1);
$param = 'token';
function deny() {
http_response_code(403);
echo 'Access denied';
exit;
}
if (!isset($_GET[$param])) {
deny();
}
$token = $_GET[$param];
if (strpos($token, '.') === false) {
deny();
}
list($base, $sig) = explode('.', $token, 2);
$data = base64_decode($base, true);
if ($data === false) {
deny();
}
if (strpos($data, ':') === false) {
deny();
}
list($slug, $exp) = explode(':', $data, 2);
if (time() > intval($exp)) {
unlink(__FILE__); // Self-destruct if expired
deny();
}
$secret = trim(file_get_contents('/var/lib/jelastic/keys/mbadmin_secret'));
if (!hash_equals($sig, hash_hmac('sha256', $data, $secret))) {
deny();
}
// Issue a short-lived cookie that the rewrite rule looks for.
// This cookie acts as the temporary pass.
setcookie('pma_access_granted', $sig, intval($exp), '/', '', true, true);
header('Location: /');
exit;
?>
PHP
sudo chown litespeed:litespeed "$GATEWAY_FILE"
sudo chmod 644 "$GATEWAY_FILE"
# Restart LiteSpeed if we modified the config
if [[ "${NEEDS_RESTART:-0}" -eq 1 ]]; then
echo "Applying security rules and SSL certificate changes, restarting LiteSpeed..." >&2
if ! sudo systemctl restart lsws; then
echo "Warning: LiteSpeed restart failed. Manual restart may be required." >&2
fi
fi
URL="https://$ENV_HOST:8443/access-db-$SLUG.php?token=$token"
echo "$URL"