f97103b05f
Important fix (sql-runner.yml): Reject multi-statement SQL — `SELECT 1; UPDATE supplier_leads ...` was passing READ_RE whitelist and executing the second statement on prod without confirm_mutating=true. Added explicit `*";"*` guard before regex checks. Minor fix (RouteSupplierLeadJob.php): Capture `$originalError = \$lead->error` BEFORE `\$lead->update(...)`. Laravel mutates the in-memory model, so reading `\$lead->error` after update returns the already-suffixed value, making Log::info `original_error` field useless for debugging. Both findings from F2 review subagent on commit c8c089cb. Test verification: 10/10 Pest GREEN (6 SupplierWebhookFastFail + 4 SingleLeadStorm).
105 lines
3.4 KiB
YAML
105 lines
3.4 KiB
YAML
name: Run whitelisted SQL on liderra.ru
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
sql:
|
|
description: 'SQL query (SELECT only by default; UPDATE/DELETE need confirm_mutating=true)'
|
|
required: true
|
|
type: string
|
|
confirm_mutating:
|
|
description: 'Подтверждаю UPDATE/DELETE на проде'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
jobs:
|
|
run:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
env:
|
|
LIDERRA_HOST: 111.88.246.137
|
|
LIDERRA_USER: ubuntu
|
|
SQL: ${{ github.event.inputs.sql }}
|
|
CONFIRM_MUT: ${{ github.event.inputs.confirm_mutating }}
|
|
|
|
steps:
|
|
- name: Whitelist check
|
|
run: |
|
|
set -euo pipefail
|
|
SQL_LOWER=$(echo "$SQL" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
|
|
# Reject multi-statement SQL — `;` would let SELECT-prefixed payloads
|
|
# smuggle UPDATE/DELETE past READ_RE without confirm_mutating=true.
|
|
# Trailing single `;` is also rejected for symmetry (use no trailing `;`).
|
|
if [[ "$SQL_LOWER" == *";"* ]]; then
|
|
echo "::error::Multi-statement SQL is not allowed (no semicolons)."
|
|
exit 1
|
|
fi
|
|
|
|
# Allow: SELECT / WITH (CTE) / \d / EXPLAIN
|
|
READ_RE='^(select |with |explain |\\d|\\df|\\di|\\dt)'
|
|
|
|
# Mutating allowed if confirm=true: targeted UPDATE/DELETE on specific tables
|
|
MUTATING_RE='^(update supplier_leads|update failed_webhook_jobs|update scheduler_heartbeats|delete from failed_webhook_jobs|delete from incidents_log) '
|
|
|
|
if [[ "$SQL_LOWER" =~ $READ_RE ]]; then
|
|
echo "::notice::SELECT/read-only — allowed."
|
|
exit 0
|
|
fi
|
|
|
|
if [[ "$SQL_LOWER" =~ $MUTATING_RE ]]; then
|
|
if [[ "$CONFIRM_MUT" != "true" ]]; then
|
|
echo "::error::Mutating SQL requires confirm_mutating=true."
|
|
exit 1
|
|
fi
|
|
echo "::warning::Mutating SQL authorized."
|
|
exit 0
|
|
fi
|
|
|
|
echo "::error::SQL not in whitelist: $SQL_LOWER"
|
|
exit 1
|
|
|
|
- name: Setup SSH key
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy
|
|
chmod 600 ~/.ssh/liderra_deploy
|
|
ssh-keyscan -H ${{ env.LIDERRA_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
|
|
|
|
- name: Run on prod
|
|
run: |
|
|
set -o pipefail
|
|
SQL_B64=$(printf '%s' "$SQL" | base64 -w0)
|
|
ssh -i ~/.ssh/liderra_deploy ${{ env.LIDERRA_USER }}@${{ env.LIDERRA_HOST }} \
|
|
"SQL_B64='$SQL_B64' bash -s" <<'REMOTE' | tee /tmp/sql.log
|
|
SQL=$(echo "$SQL_B64" | base64 -d)
|
|
echo "=== Running on $(hostname) at $(date -u) ==="
|
|
echo "SQL: $SQL"
|
|
echo
|
|
sudo -u postgres psql -d liderra -c "$SQL"
|
|
RC=$?
|
|
echo
|
|
echo "=== Exit code: $RC ==="
|
|
exit $RC
|
|
REMOTE
|
|
|
|
- name: Summary
|
|
if: always()
|
|
run: |
|
|
{
|
|
echo "## SQL on prod"
|
|
echo
|
|
echo '```sql'
|
|
echo "$SQL"
|
|
echo '```'
|
|
echo
|
|
echo '```'
|
|
cat /tmp/sql.log 2>/dev/null
|
|
echo '```'
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
run: rm -f ~/.ssh/liderra_deploy
|