SSH-Zugriff für GitHub Actions-Deployments einschränken

Ich verwende GitHub Actions immer häufiger für meine CI/CD-Pipelines. Es ist eine großartige Möglichkeit, Builds, Tests und Deployments zu automatisieren. Einer meiner Lieblingsanwendungsfälle ist das Deployment von Anwendungen auf meinem eigenen VPS. Das gibt mir die Flexibilität eines benutzerdefinierten Server-Setups mit dem Komfort eines automatisierten Deployment-Prozesses.

Allerdings birgt das Öffnen des Servers für das Internet für Deployments immer Sicherheitsrisiken. In diesem Beitrag zeige ich meinen Ansatz zur Absicherung des SSH-Zugriffs für GitHub Actions-Deployments.

Ein dedizierter Benutzer für GitHub Actions

Der erste Schritt ist die Erstellung eines dedizierten Benutzers für GitHub Actions auf deinem VPS. Dieser Benutzer sollte nur minimale Rechte haben und nur für Deployments verwendet werden. Auf diese Weise ist der Schaden eingedämmt, falls die Anmeldeinformationen des Benutzers jemals kompromittiert werden sollten.

Du kannst einen neuen Benutzer mit einem Home-Verzeichnis wie folgt anlegen:

sudo adduser --disabled-password --gecos "" github-actions

Wir verwenden das Flag --disabled-password, da wir SSH-Schlüssel zur Authentifizierung verwenden werden.

Sudoers für eingeschränkte Befehlsausführung

Der github-actions-Benutzer muss wahrscheinlich einige Befehle mit sudo ausführen, zum Beispiel um einen Dienst neu zu starten oder ein neues Docker-Image zu pullen. Anstatt dem Benutzer vollen sudo-Zugriff zu gewähren, können wir die /etc/sudoers-Datei verwenden, um genau festzulegen, welche Befehle der Benutzer ausführen darf.

Öffne dazu die sudoers-Datei mit sudo visudo und füge die folgenden Zeilen am Ende hinzu:

# Dem github-actions-Benutzer erlauben, bestimmte Befehle auszuführen
github-actions ALL=(ALL) NOPASSWD: /usr/bin/docker-compose -f /pfad/zu/deiner/docker-compose.yml pull, \
                                  /usr/bin/docker-compose -f /pfad/zu/deiner/docker-compose.yml up -d, \
                                  /usr/bin/systemctl restart dein-dienst

Diese Konfiguration erlaubt es dem github-actions-Benutzer, docker-compose pull, docker-compose up -d und systemctl restart dein-dienst mit sudo auszuführen, ohne nach einem Passwort gefragt zu werden. Achte darauf, die Pfade und Dienstnamen durch deine tatsächlichen Werte zu ersetzen.

Traefik-Middleware für IP-Whitelisting

Der effektivste Weg, den SSH-Zugriff abzusichern, besteht darin, nur Verbindungen von einer vertrauenswürdigen Gruppe von IP-Adressen zuzulassen. Da die Runner von GitHub Actions eine breite Palette von IP-Adressen verwenden, benötigen wir eine Möglichkeit, unsere Firewall-Regeln dynamisch zu aktualisieren.

Ich verwende Traefik als Reverse-Proxy, und es hat eine großartige Funktion dafür: Middlewares. Ich kann eine Middleware erstellen, die die offiziellen IP-Bereiche von GitHub Actions auf die Whitelist setzt.

Schauen wir uns zunächst die Router-Konfiguration an. Wir werden die github-actions-whitelist-Middleware auf unseren SSH-Router anwenden.

tcp:
  routers:
    ssh-router:
      entryPoints:
        - "ssh"
      rule: "HostSNI(`*`)"
      service: "sshd-service"
      middlewares:
        - "github-actions-whitelist"
      tls: {}

  services:
    sshd-service:
      loadBalancer:
        servers:
          - address: "172.17.0.1:22"

Die github-actions-whitelist-Middleware selbst wird jetzt in einer separaten Datei verwaltet, die dynamisch generiert wird. Diese Trennung hält die Hauptkonfiguration sauber und ermöglicht automatisierte Updates.

Automatisierung der IP-Listen-Updates

Um die IP-Liste aktuell zu halten, können wir ein einfaches Bash-Skript verwenden, das die neuesten IP-Bereiche von der GitHub-API abruft und die Middleware-Konfigurationsdatei generiert.

Hier ist das Skript zum Generieren von traefik/config/dynamic/middleware_ipAllowList_github_actions.yml:

#!/usr/bin/env bash
set -euo pipefail

# Generates a Traefik dynamic config YAML with GitHub Actions IP allow list
# Output: traefik/config/dynamic/middleware_ipAllowList_github_actions.yml
# Requires: curl, jq

SCRIPT_DIR="$(cd "${BASH_SOURCE[0]%/*}" && pwd)"
OUT_FILE="$SCRIPT_DIR/config/dynamic/middleware_ipAllowList_github_actions.yml"
TMP_FILE="${OUT_FILE}.tmp"

if ! command -v curl >/dev/null 2>&1; then
  echo "Error: curl is required" >&2
  exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
  echo "Error: jq is required" >&2
  exit 1
fi

mkdir -p "$(dirname "$OUT_FILE")"

# Fetch GitHub meta with a User-Agent header to avoid 403
JSON=$(curl -fsSL -H "User-Agent: gh-actions-ip-allowlist-script" "https://api.github.com/meta")

# Extract actions CIDRs (both IPv4 and IPv6) and sort
mapfile -t CIDRS < <(echo "$JSON" | jq -r '.actions[]' | sort -u)

if [ ${#CIDRS[@]} -eq 0 ]; then
  echo "Error: no actions CIDRs found from GitHub meta" >&2
  exit 1
fi

# Write YAML
{
  echo "# Generated by generate_github_actions_ipallowlist.sh on $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
  echo "tcp:"
  echo "  middlewares:"
  echo "    github-actions-whitelist:"
  echo "      ipWhiteList:"
  echo "        sourceRange:"
  for cidr in "${CIDRS[@]}"; do
    printf "          - \"%s\"\n" "$cidr"
  done
} > "$TMP_FILE"

mv "$TMP_FILE" "$OUT_FILE"
echo "Wrote $OUT_FILE with ${#CIDRS[@]} CIDRs"

Planung mit Cron

Um sicherzustellen, dass die IP-Liste immer auf dem neuesten Stand ist, kannst du dieses Skript so planen, dass es regelmäßig mit einem Cron-Job ausgeführt wird. Um es beispielsweise einmal täglich auszuführen, öffne deine Crontab mit crontab -e und füge die folgende Zeile hinzu:

0 0 * * * /pfad/zu/deinen/skripten/generate_github_actions_ipallowlist.sh

Achte darauf, /pfad/zu/deinen/skripten/ durch den tatsächlichen Pfad zu deinem Skript zu ersetzen.

Durch die Kombination eines dedizierten Benutzers, einer restriktiven sudoers-Konfiguration und einer dynamisch aktualisierten IP-Whitelist-Middleware kann ich meine Anwendungen von GitHub Actions sicher auf meinem VPS bereitstellen. Es ist ein wenig Einrichtungsaufwand, aber die gewonnene Sicherheit ist es absolut wert.

Share this post: