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

324 lines
11 KiB
Bash
Raw Normal View History

2025-08-26 16:26:33 +00:00
#!/bin/bash
2025-10-20 18:47:40 +00:00
2025-08-26 16:26:33 +00:00
# ==============================================================================
# Script: create_pma_gateway.sh
# Purpose: Create a time-limited gateway URL for phpMyAdmin on Virtuozzo LLSMP.
2025-10-20 18:47:40 +00:00
# Uses Let's Encrypt cert in the dedicated phpMyAdmin vhost (port 8443).
# Dynamically detects Let's Encrypt certificate paths.
2025-08-26 16:26:33 +00:00
# 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#*=}" ;;
2025-10-20 17:51:39 +00:00
*) echo "ERROR: Unknown argument $arg"; exit 1 ;;
2025-08-26 16:26:33 +00:00
esac
done
if [[ -z "$SLUG" ]]; then
SLUG=$(openssl rand -hex 4) # 8-char random
fi
2025-10-20 18:47:40 +00:00
# Determine environment public host (no node prefix) - used for certificate lookup and URL
2025-10-20 17:59:54 +00:00
if [[ -n "${JELASTIC_ENV_DOMAIN:-}" ]]; then
2025-10-20 18:47:40 +00:00
ENV_HOST="$JELASTIC_ENV_DOMAIN"
2025-10-20 17:59:54 +00:00
else
ENV_HOST=$(hostname -f)
ENV_HOST=${ENV_HOST#node*-} # strip nodeXXXX-
2025-10-20 15:36:39 +00:00
fi
2025-08-26 16:26:33 +00:00
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
2025-08-26 17:27:43 +00:00
sudo chown litespeed:litespeed "$SECRET_FILE"
sudo chmod 644 "$SECRET_FILE"
2025-10-20 18:47:40 +00:00
SECRET=$(sudo cat "$SECRET_FILE" | xargs)
2025-08-26 16:26:33 +00:00
now=$(date +%s)
expires=$((now + VALIDITY*60))
# token = base64("$SLUG:$expires") . '.' . HMAC_SHA256(secret, data)
data="$SLUG:$expires"
2025-08-26 17:27:43 +00:00
base=$(printf "%s" "$data" | base64 | tr -d '\n')
2025-10-20 18:47:40 +00:00
mac=$(php -r "echo hash_hmac('sha256', '$data', '$SECRET');")
token="$base.$mac"
2025-10-20 17:51:39 +00:00
2025-10-20 18:47:40 +00:00
# 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
2025-10-20 17:51:39 +00:00
fi
2025-10-20 18:47:40 +00:00
# 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
2025-08-26 16:26:33 +00:00
2025-10-20 18:47:40 +00:00
# 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
2025-08-26 17:50:45 +00:00
2025-10-20 18:03:05 +00:00
2025-10-20 18:47:40 +00:00
# If vhost config is missing or empty, recreate it from a known-good default WITH Let's Encrypt Certs.
2025-10-20 18:03:05 +00:00
if [ ! -s "$VHOST_CONFIG" ]; then
2025-10-20 18:47:40 +00:00
echo "Warning: $VHOST_CONFIG is empty or missing. Recreating from default with Let's Encrypt certs." >&2
sudo tee "$VHOST_CONFIG" > /dev/null <<EOF
2025-10-20 18:03:05 +00:00
<?xml version="1.0" encoding="UTF-8"?>
<virtualHostConfig>
<docRoot>/usr/share/phpMyAdmin/</docRoot>
<enableGzip>1</enableGzip>
<logging>
<log>
<useServer>0</useServer>
2025-10-20 18:47:40 +00:00
<fileName>\$SERVER_ROOT/logs/error.log</fileName>
2025-10-20 18:03:05 +00:00
<logLevel>DEBUG</logLevel>
<rollingSize>10M</rollingSize>
</log>
<accessLog>
<useServer>0</useServer>
2025-10-20 18:47:40 +00:00
<fileName>\$SERVER_ROOT/logs/access.log</fileName>
2025-10-20 18:03:05 +00:00
<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>
2025-10-20 18:47:40 +00:00
<cacheStorePath>/tmp/lscache/vhosts/\$VH_NAME</cacheStorePath>
2025-10-20 18:03:05 +00:00
</storage>
</cache>
<rewrite>
<enable>0</enable>
<logLevel>0</logLevel>
<rules>RewriteCond %{HTTP_USER_AGENT} ^NameOfBadRobot
RewriteRule ^/nospider/ - [F]</rules>
</rewrite>
<vhssl>
2025-10-20 18:47:40 +00:00
<keyFile>$LE_KEY_FILE</keyFile>
<certFile>$LE_CERT_FILE</certFile>
2025-10-20 18:03:05 +00:00
<certChain>1</certChain>
</vhssl>
<frontPage>
<enable>0</enable>
<disableAdmin>0</disableAdmin>
</frontPage>
<awstats>
<updateMode>0</updateMode>
2025-10-20 18:47:40 +00:00
<workingDir>\$VH_ROOT/awstats</workingDir>
2025-10-20 18:03:05 +00:00
<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
2025-10-20 17:51:39 +00:00
fi
2025-10-20 18:03:05 +00:00
if [ -f "$VHOST_CONFIG" ]; then
MARKER="# PMA Gateway Security Rules"
2025-10-20 18:47:40 +00:00
2025-10-20 18:03:05 +00:00
# If rules are not already in place, add them.
if ! sudo grep -qF "$MARKER" "$VHOST_CONFIG"; then
2025-10-20 18:47:40 +00:00
2025-10-20 18:03:05 +00:00
# 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'
2025-08-26 18:07:06 +00:00
# PMA Gateway Security Rules
# Allow access to the gateway scripts themselves
RewriteCond %{REQUEST_URI} ^/access-db-.*\.php$
RewriteRule .* - [L]
2025-10-20 18:03:05 +00:00
# For all other requests, block if the security cookie is not present
2025-08-26 18:07:06 +00:00
RewriteCond %{HTTP_COOKIE} !pma_access_granted
RewriteRule .* - [F,L]
2025-08-26 17:46:27 +00:00
EOF
2025-10-20 18:03:05 +00:00
)
2025-10-20 18:47:40 +00:00
2025-10-20 18:03:05 +00:00
# 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
2025-08-26 17:46:27 +00:00
fi
2025-10-20 18:47:40 +00:00
# --- 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
2025-10-20 18:03:05 +00:00
else
2025-10-20 18:47:40 +00:00
echo "Warning: phpMyAdmin vhost config not found at $VHOST_CONFIG. Cannot apply security rules or update SSL." >&2
2025-08-26 17:46:27 +00:00
fi
2025-10-20 18:47:40 +00:00
2025-08-26 17:27:43 +00:00
sudo tee "$GATEWAY_FILE" >/dev/null <<'PHP'
2025-08-26 16:26:33 +00:00
<?php
2025-08-26 17:27:43 +00:00
// Secure phpMyAdmin gateway auto-generated, do NOT edit manually.
2025-08-26 16:26:33 +00:00
ini_set('session.cookie_httponly', 1);
2025-08-26 17:27:43 +00:00
$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();
}
2025-08-26 16:26:33 +00:00
2025-08-26 17:27:43 +00:00
list($base, $sig) = explode('.', $token, 2);
2025-08-26 16:26:33 +00:00
$data = base64_decode($base, true);
2025-08-26 17:27:43 +00:00
if ($data === false) {
deny();
}
if (strpos($data, ':') === false) {
deny();
}
list($slug, $exp) = explode(':', $data, 2);
if (time() > intval($exp)) {
2025-08-26 17:46:27 +00:00
unlink(__FILE__); // Self-destruct if expired
2025-08-26 17:27:43 +00:00
deny();
}
$secret = trim(file_get_contents('/var/lib/jelastic/keys/mbadmin_secret'));
if (!hash_equals($sig, hash_hmac('sha256', $data, $secret))) {
deny();
}
2025-08-26 17:46:27 +00:00
// 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);
2025-10-20 18:47:40 +00:00
header('Location: /');
2025-08-26 16:26:33 +00:00
exit;
?>
PHP
2025-08-26 17:27:43 +00:00
sudo chown litespeed:litespeed "$GATEWAY_FILE"
sudo chmod 644 "$GATEWAY_FILE"
2025-08-26 16:26:33 +00:00
2025-08-26 17:46:27 +00:00
# Restart LiteSpeed if we modified the config
if [[ "${NEEDS_RESTART:-0}" -eq 1 ]]; then
2025-10-20 18:47:40 +00:00
echo "Applying security rules and SSL certificate changes, restarting LiteSpeed..." >&2
2025-08-26 17:46:27 +00:00
if ! sudo systemctl restart lsws; then
echo "Warning: LiteSpeed restart failed. Manual restart may be required." >&2
fi
fi
2025-10-20 18:47:40 +00:00
URL="https://$ENV_HOST:8443/access-db-$SLUG.php?token=$token"
echo "$URL"