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

312 lines
10 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.
# 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 "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)
if [[ -n "${JELASTIC_ENV_DOMAIN:-}" ]]; then
ENV_HOST="$JELASTIC_ENV_DOMAIN"
else
ENV_HOST=$(hostname -f)
ENV_HOST=${ENV_HOST#node*-} # strip nodeXXXX-
fi
# ==============================================================================
# Step 1: Ensure xmlstarlet is installed for safe XML parsing
# ==============================================================================
if ! command -v xmlstarlet &> /dev/null; then
echo "xmlstarlet not found. Installing for safe XML parsing..." >&2
if ! sudo dnf install -y xmlstarlet; then
echo "FATAL: Failed to install xmlstarlet. Cannot safely read LiteSpeed config." >&2
exit 1
fi
fi
# ==============================================================================
# Step 2: Dynamically read SSL configuration from main LiteSpeed config
# ==============================================================================
LITESPEED_CONFIG="/var/www/conf/httpd_config.xml"
KEY_FILE_PATH=""
CERT_FILE_PATH=""
if [[ -f "$LITESPEED_CONFIG" ]]; then
echo "Reading SSL configuration from LiteSpeed main config..." >&2
# Query the main HTTPS listener (port 443) for keyFile and certFile
# This is the most specific and robust XPath
KEY_FILE_PATH=$(sudo xmlstarlet sel -t -v \
"//httpServerConfig/listenerList/listener[name='HTTPS' and secure='1' and address='*:443'][1]/keyFile" \
"$LITESPEED_CONFIG" 2>/dev/null | xargs)
CERT_FILE_PATH=$(sudo xmlstarlet sel -t -v \
"//httpServerConfig/listenerList/listener[name='HTTPS' and secure='1' and address='*:443'][1]/certFile" \
"$LITESPEED_CONFIG" 2>/dev/null | xargs)
fi
# ==============================================================================
# Step 3: Implement fallback to default self-signed certificate
# ==============================================================================
if [[ -z "$KEY_FILE_PATH" ]] || [[ -z "$CERT_FILE_PATH" ]]; then
echo "No custom SSL certificate found. Falling back to default self-signed certificate." >&2
# Use SINGLE quotes to write the literal string "$SERVER_ROOT" to the config,
# not the shell variable. This is critical.
KEY_FILE_PATH='$SERVER_ROOT/ssl/litespeed.key'
CERT_FILE_PATH='$SERVER_ROOT/ssl/litespeed.crt'
else
echo "Using SSL certificate: $CERT_FILE_PATH" >&2
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
# If vhost config is missing or empty, recreate it from a known-good default.
if [ ! -s "$VHOST_CONFIG" ]; then
echo "Warning: $VHOST_CONFIG is empty or missing. Recreating from default." >&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>__KEY_FILE_PLACEHOLDER__</keyFile>
<certFile>__CERT_FILE_PLACEHOLDER__</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
# ==============================================================================
# Step 5: Inject the discovered certificate paths using sed
# ==============================================================================
# Escape special characters (/, $, &, \, ') in paths for use with sed
ESCAPED_KEY_PATH=$(printf '%s\n' "$KEY_FILE_PATH" | sed 's/[\/&$\\'"'"']/\\&/g')
ESCAPED_CERT_PATH=$(printf '%s\n' "$CERT_FILE_PATH" | sed 's/[\/&$\\'"'"']/\\&/g')
# Replace placeholders with actual certificate paths
sudo sed -i "s|__KEY_FILE_PLACEHOLDER__|$ESCAPED_KEY_PATH|g" "$VHOST_CONFIG"
sudo sed -i "s|__CERT_FILE_PLACEHOLDER__|$ESCAPED_CERT_PATH|g" "$VHOST_CONFIG"
echo "SSL certificate paths injected into vhost configuration." >&2
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
else
echo "Warning: phpMyAdmin vhost config not found at $VHOST_CONFIG. Cannot apply security rules." >&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 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"