Contact salesFree trial
Blog

Bereinigung von Vorschau-Umgebungsdaten

gdprDatenAktivitätsskripteAutomatisierung
Teilen Sie

Die Einhaltung der GDPR in all Ihren Projekten ist eine tägliche Herausforderung - vor allem, wenn Sie sensible Nutzerdaten in Ihren Projekten verwalten. Upsun verfolgt einen "GDPR everywhere" -Ansatz mit einem hohen Maß an eingebauter Sicherheit und Konformität als Standard - aber es gibt Möglichkeiten, Ihre Daten auf unserer PaaS weiter zu sichern , wenn es um Vorschauumgebungen geht.

Jedes Mal, wenn Sie einen neuen Git-Zweig für ein Projekt auf Upsun erstellen, erbt die entsprechende Umgebung die Daten (Assets und Datenbank) von ihrer Muttergesellschaft. Das bedeutet, dass potenziell sensible Daten von Ihrer Produktionswebsite in der Vorschauumgebung sichtbar werden könnten.

Wie können Sie also damit umgehen und sicherstellen, dass Ihre Anwendung konform bleibt? Zwei Worte: Datenbereinigung. Die absichtliche und dauerhafte Löschung sensibler Daten von einem Speichergerät, so dass die Daten nicht wiederhergestellt werden können. In diesem Artikel stelle ich Ihnen die Methoden der Datenbereinigung vor, die Sie in Vorschauumgebungen implementieren können , um sicherzustellen, dass Ihre Daten in jeder Phase der Entwicklung sicher sind.

Einige notwendige Ressourcen, bevor wir beginnen

Es gibt einige Voraussetzungen, die sicherstellen, dass Sie die in diesem Artikel beschriebenen Lösungen und Schritte befolgen können - bitte stellen Sie sicher, dass Sie Folgendes installiert haben:

Methoden zur Bereinigung von Anwendungsdaten

In diesem Artikel konzentrieren wir uns auf die Bereinigung von Umgebungsdaten in der Vorschau von Symfony, die beschriebenen Methoden gelten jedoch für alle Frameworks.

Wenn Sie mehr darüber erfahren möchten, wie Sie Symfony-Demo-Anwendungen auf Upsun einrichten, werfen Sie einen Blick auf unseren Leitfaden Up(sun) and running with Symfony Demo.

Im Laufe des Artikels gehen wir durch die verschiedenen Methoden, die zur Bereinigung von Symfony-Vorschau-Umgebungsdaten auf Upsun zur Verfügung stehen - 5 Methoden, um genau zu sein -, die Sie abwägen und die beste für Sie auswählen können. Stellen Sie jedoch sicher, dass Sie den Schritt "create a command" abschließen , bevor Sie mit einer Methode fortfahren.

Wenn Sie bereits wissen, welche Methode Sie bevorzugen, klicken Sie einfach auf den entsprechenden Titel unten und wir bringen Sie direkt dorthin:

Das Wichtigste zuerst: Erstellen Sie einen Befehl zur Bereinigung Ihrer Daten

Bitte beachten Sie: Wenn Sie einen anderen Stack als Symfony hosten, passen Sie bitte den Befehl zur Bereinigung Ihrer Datenbank in Ihrem Stack an und übertragen Sie ihn in Ihren Produktionszweig. Dies ist der einzige Symfony-spezifische Schritt in diesem Artikel.

Um eine der fünf oben aufgeführten Methoden zur Datenbereinigung auszuführen, benötigen wir eine Callable, um unsere Umgebungen zu bereinigen. Es gibt zwei Möglichkeiten, dies zu tun:

  1. Verwendung eines SQL-Skripts zur Aktualisierung oder Fälschung aller sensiblen Daten
  2. Verwendung eines Symfony-Befehls, vielleicht unter Verwendung des fakerPHP-Pakets

Da wir eine Symfony-Demo-Anwendung verwenden, werden wir die zweite Option nutzen. Führen Sie die folgenden Schritte aus dem Haupt-Git-Zweig aus:

symfony composer require --dev fakerphp/faker git add composer.json composer.lock && git commit -m "composer require --dev fakerphp bundle" 

Öffnen Sie dann Ihren Code in Ihrer bevorzugten IDE und erstellen Sie einen neuen Symfony-Befehl in einer Datei SRC/command/SanitizeDataCommand.php mit folgendem Inhalt:

<?php /* src/Command/SanitizeDataCommand.php */ namespace App\Command; use App\Entity\User; use App\Repository\UserRepository; use Doctrine\ORM\EntityManagerInterface; use Faker; use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; #[AsCommand( name: 'app:sanitize-data', description: 'Sanitize user data (username and email).', aliases: ['app:sanitize'] )] class SanitizeDataCommand extends Command { private SymfonyStyle $io; public function __construct(private UserRepository $userRepository, private EntityManagerInterface $entityManager) { parent::__construct(); } protected function configure() { $this ->setDescription('Mit diesem Befehl können Sie die Benutzerdaten (Benutzername und E-Mail) bereinigen.'); } protected function initialize(InputInterface $input, OutputInterface $output): void { $this->io = new SymfonyStyle($input, $output); } protected function execute(InputInterface $input, OutputInterface $output): int { $users = $this->userRepository->findAll(); $this->io->progressStart(count($users)); $this->entityManager->getConnection()->beginTransaction(); // Auto-Commit aussetzen try { /** @var User $user */ foreach ($users as $user) { $this->io->progressAdvance(); // Faker initialisieren $faker = Faker\Factory::create(); $this->io->text('faking user '.$user->getUsername()); // Benutzerdaten fälschen $user->setUsername(uniqid($faker->userName())); $user->setEmail($faker->email()); // bitte an Ihre Bedürfnisse anpassen }   

           $this->entityManager->flush(); $this->entityManager->getConnection()->commit(); $this->io->progressFinish(); } catch (\Exception $e) { $this->entityManager->getConnection()->rollBack(); throw $e; } return Command::SUCCESS; } }

Dieser Befehl app:sanitize-data verwendet das UserRepository und fälscht den Benutzernamen und die E-Mail aus der Symfony-Standardentität User. Bitte passen Sie ihn an Ihre Bedürfnisse an. Dann pushen Sie Ihren Code in den Hauptzweig:

git add src/Command/SanitizeDataCommand.php && git commit -m "sanitize data command" 
symfony einsetzen

1) Manuelles Bereinigen Ihrer Daten

Da Ihr Quellcode nun einen Symfony-Befehl zur Datenbereinigungenthält , werden wir ihn manuell in einer neuen Vorschauumgebung verwenden. Beginnen Sie mit der Erstellung eines neuen Staging-Zweiges und warten Sie, bis der Prozess abgeschlossen ist, wie folgt:

symfony branch staging --type=staging

Führen Sie dann den neu erstellten Symfony-Befehl in Ihrer Upsun-Staging-Umgebung aus, wie unten dargestellt:

symfony ssh php bin/console -e dev app:sanitize-data

Et voilà, die Daten Ihrer Vorschauumgebung sind bereinigt!

2) Umgebungsvererbung verwenden

In diesem Abschnitt werden wir eine Vorschauumgebung erstellen, ihre Daten bereinigen und dann alle neuen Umgebungen von dieser Vorschauumgebung erben lassen.

Wie bereits am Anfang dieses Artikels erwähnt, erbt die erstellte Umgebung jedes Mal, wenn Sie einen neuen Git-Zweig auf Upsun erstellen, die Daten der übergeordneten Umgebung. Es ist jedoch möglich, die standardmäßige Datenvererbung zu ändern und sie später so einzustellen, dass sie Daten von einer neuen Elternumgebung synchronisiert - der Vorschauumgebung, die wir erstellen werden.

Die Symfony CLI bietet die Möglichkeit, einen Zweig ohne Elternteil zu erstellen, indem die Option --no-clone-parent verwendet wird , und dann den Elternteil auf staging (auch bekannt als preview) zu setzen, was sicherstellt, dass neue Zweige die Daten der Preview-Umgebung erben. Folgen Sie den Anweisungen in Schritt 1, um zu erfahren, wie Sie die Daten der Preview-Umgebung manuell bereinigen, um sicherzustellen, dass alle zukünftigen Zweige bereinigte, GDPR-konforme Daten erben.

symfony checkout main symfony branch dev --no-clone-parent symfony env:info -e dev parent staging symfony sync -e dev data

Und das war's. Ihre neue Entwicklungsumgebung wird nun mit den bereinigten Daten aus Ihrer Vorschauumgebung erstellt.

3) Verwenden Sie einen Hook

Anstatt sich auf die Vererbung zu verlassen, kann es wünschenswert sein, bestimmte Daten bei jeder Bereitstellung zu bereinigen. In diesem Fall können wir unseren Skriptaufruf in den Abschnitt hooks der Konfiguration verschieben .

Für welche Art von Hook Sie sich entscheiden- Deploy- oder Post-Deploy-Hooks -, bleibt Ihnen überlassen, aber es gibt einige Dinge zu beachten:

  • Ein langlaufendes Skript innerhalb des Deploy-Hooks muss die Bereitstellungszeit einer Anwendung verlängern.
  • Ein langlaufendes Skript im post_deploy-Hook könnte nicht konforme/kritische Daten vorübergehend öffentlich machen, während die Bereinigung stattfindet.
  • Redeploys: Wenn Sie die Bereinigung manuell mit einem Redeploy auslösen möchten, findet die Bereinigung bei jedem Redeploy nur statt, wenn sie im post_deploy-Hook platziert ist.

Um einen Symfony-Befehl während des post_deploy Hooks auszuführen , fügen Sie das Folgende in Ihre .upsun/config.yaml ein :

applications: app: hooks: build: ... deploy: ... post_deploy: | if [ "$PLATFORM_ENVIRONMENT_TYPE" != production ]; then # Die Bereinigung der Datenbank sollte hier stattfinden (da sie nicht produktiv ist) php bin/console -e dev app:sanitize-data fi

Pushen Sie dann Ihren Code in den Hauptzweig:

git checkout main && git add .upsun/config.yaml && git commit -m "add sanitize data command to post_deploy hook" 
symfony deploy

4) Laufzeitoperationen und Aktivitätsskripte verwenden

Es gibt eine weitere Option, mit der Sie einen benutzerdefinierten Trigger erstellen können, der als Reaktion auf bestimmte Aktivitäten im Projekt ausgeführt wird. Bei der Synchronisierung einer Umgebung mit der übergeordneten Umgebung könnten wir nämlich nicht anonymisierte Daten aus der übergeordneten Umgebung zurücksynchronisieren (z. B. bei der Synchronisierung aus der Produktionsumgebung).

Die beiden Komponenten, die dies ermöglichen, sind:

  • Eine Runtime-Operation: Sie ermöglicht es Ihnen, einmalige Befehle oder Skripte für Ihr Projekt auszulösen. Ähnlich wie Crons laufen sie im Anwendungscontainer, aber nicht nach einem bestimmten Zeitplan.
  • Ein Aktivitätsskript: ein Stück JavaScript-Code, das als Reaktion auf bestimmte Aktivitäten auf Projekt-, Umgebungs- oder sogar Organisationsebene ausgeführt wird.

Wir fügen also eine Integration (Aktivitätsskript) hinzu, die auf bestimmte Ereignisse reagiert, um eine Laufzeitoperation zur Datenbereinigung im laufenden Betrieb auszuführen, siehe Integration eines Aktivitätsskriptshinzufügen unten.

Bitte beachten Sie: Wenn Sie im vorherigen Schritt einen post_deploy-Hook gesetzt haben, kommentieren Sie ihn bitte aus, da er nach Verwendung dieser Laufzeitoperation nicht mehr benötigt wird.

So erstellen Sie eine Runtime-Operation

Um eine Runtime-Operation zu konfigurieren, müssen wir einen neuen Top-Level YAML-Schlüssel in unserer .upsun/config. yaml-Datei mit folgendem Inhalthinzufügen :

applications: app: 
    operations: sanitize: role: admin commands: start: | if [ "$PLATFORM_ENVIRONMENT_TYPE" != production ]; then # Die Bereinigung der Datenbank sollte hier stattfinden (da sie nicht produktiv ist) php bin/console -e dev app:sanitize-data fi

Pushen Sie dann Ihre Datei in den Hauptzweig und stellen Sie sie bereit.

git checkout main git add .upsun/config.yaml && git commit -m "add runtime operation to sanitize data" symfony deploy

Wenn Sie diese Runtime-Operation manuell testen möchten, können Sie Folgendes verwenden:

symfony operation:run sanitize --app=app

Wie man ein Aktivitätsskript erstellt

Upsun unterstützt benutzerdefinierte Skripte, die als Reaktion auf eine beliebige Aktivität ausgelöst werden können. Dieses Skript wird außerhalb des Umgebungskontextes ausgeführt. Daher müssen wir diesen Kontext neu erstellen, damit das Aktivitätsskript mit den erforderlichen Rechten ausgeführt werden kann. Erstellen Sie dazu eine neue Datei src/runtime/sanitize.js mit folgendem Inhalt:

// src/runtime/sanitize.js let app_container = "app"; let runtime_operation_name = "sanitize"; if (!variables.api_token) { console.log("Variable API Token ist nicht definiert!"); console.log("Bitte definieren Sie eine Umgebungsvariable mit Ihrem API-Token mit dem Befehl: "); console.log("upsun project:curl /integrations/<INTEGRATION_ID>/variables -X POST -d '{\"name\": \"api_token\", \"value\": \"<API_TOKEN>\", \"is_sensitive\": true, \"is_json\": false}' "); } else { console.log("OAuth2 API Token definiert"); let resp = fetch('https://auth.api.platform.sh/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: "client_id=platform-api-user&grant_type=api_token&api_token=" + variables.api_token }); if (!resp.ok) { console.log("Failed to get an OAuth2 token, status code was " + resp.status); } else { console.log("OAuth2 API TOKEN ok"); } let access_token = resp.json().access_token; // get current branch from activity object let branch; switch (activity.type) { case 'environment.synchronize': branch = activity.parameters.into; break; case 'environment.branch': case 'environment.activate': branch = activity.parameters.environment; break; } // Laufzeitoperation runtime_operation_name auf aktuelle/gezielte Umgebung ausführen resp = fetch("https://api.upsun.com/api/projects/" + activity.project + "/environments/" + branch + "/deployments/current/operations", { headers: { "Authorization": "Bearer " + access_token }, method: "POST", body: JSON.stringify({"service": app_container, "operation": runtime_operation_name}), }); if (!resp.ok) { console.log("Failed to invoke the runtime operation, status code was " + resp.status); } else { console.log(runtime_operation_name + " launched"); } }

Dieses Aktivitätsskript verwendet ein API-Token als Umgebungsvariable, um sich mit der aktuellen Umgebung zu verbinden und die zuvor definierte Laufzeitoperation mithilfe der Upsun-API auszuführen . Wir müssen diese Umgebungsvariable für die Integration unseres Aktivitätsskripts definieren und später eine API Token Umgebungsvariable hinzufügen.

Dann schieben Sie Ihre Datei in den Hauptzweig und verteilen Sie sie wie folgt:

symfony checkout main git add src/runtime/sanitize.js git commit -m "add activity script" symfony deploy

Hinzufügen einer Integration eines Aktivitätsskripts

Drei Upsun-Ereignisse sollten diese Laufzeitoperation auslösen:

  • Beim Erstellen eines neuen Zweigs (environment.branch),
  • Wenn eine Synchronisation von Daten zwischen Umgebungen stattfindet (environment.synchronize),
  • Wenn eine Vorschauumgebung aktiviert wird (environment.activate).

Um diese Auslöser zu implementieren, verwenden Sie diesen Befehl in Ihrem Terminal, um eine Aktivitätsskript-Integration hinzuzufügen.

symfony integration:add --type script --file ./src/runtime/sanitize.js --events environment.branch,environment.synchronize,environment.activate --states complete --environments \*

Hinweis: Eine vollständige Liste der möglichen Ereignisse ist in der Definition des Typs des Aktivitätsskripts verfügbar. Jeder dieser Aktivitätsskripttypen kann als Ereignisliste mit der Option --events=event1,event2,... hinzugefügt werden.

Hinzufügen einer API-Token-Umgebungsvariable

Ermitteln Sie zunächst die vorherige Integrations-ID mit dem folgenden Befehl:

symfony integration:list

Erstellen Sie dann ein neues API-Token in der Konsole, behalten Sie den Wert in der Hand und ersetzen Sie ihn in diesem Terminalbefehl:

symfony project:curl /integrations/<INTEGRATION_ID>/variables -X POST -d '{"name": "api_token", "value": "<API_TOKEN>", "is_sensitive": true, "is_json": false}'

Bitte beachten Sie: Ersetzen Sie <INTEGRATION_ID> und <API_TOKEN> durch die entsprechenden, zuvor erstellten Werte.

Sie können mit diesem Befehl überprüfen, ob die Variable erstellt wurde:

symfony project:curl /integrations/<INTEGRATION_ID>/variables

Zeit zum Testen

Um zu testen, ob alles funktioniert hat, lösen Sie in der Konsole oder mit der CLI die Erstellung eines neuen Zweigs von main aus, lösen Sie eine Synchronisierung aus, deaktivieren und reaktivieren Sie Ihre Vorschauumgebung, und dann sollten Sie zwei Aktivitäten sehen:

  • Ausgelöste Aktivität
  • Eine Laufzeitaktivität

Sie sind auf ein Problem gestoßen? Beseitigen Sie es

Wenn Sie auf ein Problem stoßen und die Integration des Aktivitätsskripts debuggen möchten, müssen Sie den folgenden Befehl verwenden:

symfony integration:activity:log <INTEGRATION_ID>

Wenn Sie die Integration Ihres Aktivitätsskripts hinzufügen, wird das entsprechende Skript auf der Upsun-Seite im Speicher hinzugefügt. Das bedeutet, dass Sie jedes Mal, wenn Sie Ihr Skript aktualisieren, die zwischengespeicherte Version der Datei aktualisieren müssen, indem Sie den folgenden Befehl verwenden:

symfony integration:update <INTEGRATION_ID> --file ./src/runtime/sanitize.js

Bitte beachten Sie: Um Ihren Quellcode auf dem neuesten Stand zu halten, müssen Sie diese Datei natürlich committen:

git add src/runtime/sanitize.js git commit -m "add activity script" symfony deploy # optional

5) Verwendung von Shell-Skripten zur Bereinigung von Entwicklungsumgebungen

Es ist möglich, ein Shell-Skript zu verwenden, um die Datenbereinigung aller Umgebungen außer der Produktionsumgebung für alle Projekte innerhalb einer Organisation zu automatisieren - erfahren Siehier mehr über Organisationen. Um dieses Shell-Skript zu verwenden, stellen Sie bitte sicher, dass alle Umgebungsquellen aller Ihrer Projekte innerhalb Ihrer Organisation den Symfony-Befehl zur Datenbereinigung enthalten , bevor Sie die folgenden Schritte durchführen.

Der erste Schritt besteht darin, eine Datei namens fleet_sanitizer.sh mit dem folgenden Code zu erstellen:

if [ -n "$ZSH_VERSION" ]; then emulate -L ksh; fi ###################################################### # fleet sanitization demo script, using the CLI.
# # Aktiviert den folgenden Arbeitsablauf für ein bestimmtes Projekt und reinigt Vorschauumgebungen (Staging-, New-Feature- und Auto-Updates-Umgebung): # . # └── main # ├── staging # | └── new-feature # └── auto-updates # # Verwendung # 1. source this script: `. fleet_sanitizer.sh` oder `source fleet_sanitizer.sh`, abhängig von Ihrem lokalen Rechner # 2. define ORGANIZATION var: ORGANIZATION=<organizationIdentifier> # 3. run `sanitize_organization_data $ORGANIZATION` ###################################################### # Utility functions. # list_org_projects: Gibt eine Liste der Projekte aus, auf die die Operation vor dem Start angewendet wird. # $1: Organisation, wie sie in console.upsun.com erscheint. list_org_projects() { symfony project:list -o $1 --columns="ID, Title" } # get_org_projects: Liefert ein Array von Projekt-IDs für eine bestimmte Organisation. # Hinweis: Macht die Array-Variable PROJECTS für nachfolgende Skripte verfügbar. # $1: Organisation, wie sie in console.upsun.com erscheint. get_org_projects() { PROJECTS_LIST=$(symfony project:list -o $1 --pipe) PROJECTS=($PROJECTS_LIST) } # get_project_envs: Ruft ein Array von Umgebungs-IDs für ein Projekt ab. # Hinweis: Macht die Array-Variable ENVS für nachfolgende Skripte verfügbar. # $1: Projekt-ID, wie sie in console.upsun.com erscheint. get_project_envs() { ENV_LIST=$(symfony environment:list -p $1 --pipe) ENVS=($ENV_LIST) } # list_project_envs: Liste der Umgebungen ausgeben, auf die die Operation vor dem Start angewendet wird. # $1: ProjectId, wie sie in console.upsun.com erscheint. list_project_envs() { symfony environment:list -p $1 } # add_env_var: Umgebungsvariable auf Umgebungsebene hinzufügen. # $1: Variablenname. # $2: Variablenwert. # $3: Zielprojekt-ID. # $4: Zielumgebungs-ID. add_env_var() { VAR_STATUS=$(symfony project:curl -p $3 /environments/$4/variables/env:$1 | jq '.status') if [ "$VAR_STATUS" != "null" ]; then symfony variable:create --name $1 --value "$2" --prefix env: --project $3 --environment $4 --level environment --json false --sensitive false --visible-build true --visible-runtime true --enabled true --inheritable true -q else printf "\nVariable $1 existiert bereits. Skipping." fi } # Hauptfunktionen. sanitize_organization_data() { list_org_projects $1 get_org_projects $1 for PROJECT in "${PROJECTS[@]}"; do printf "\n### Project $PROJECT." # get environments list list_project_envs $PROJECT get_project_envs $PROJECT for ENVIRONMENT in "${ENVS[@]}"; do unset -f ENV_CHECK ENV_CHECK=$(symfony project:curl -p $PROJECT /environments/$ENVIRONMENT | jq -r '.status') unset -f ENV_TYPE ENV_TYPE=$(symfony project:curl -p $PROJECT /environments/$ENVIRONMENT | jq -r '.type') if [ "$ENV_CHECK" = active -a "$ENV_TYPE" != production ]; then unset -f DATA_SANITIZED DATA_SANITIZED=$(symfony variable:get -p $PROJECT -e $ENVIRONMENT env:DATA_SANITIZED --property=value) if [ "$DATA_SANITIZED" != true ]; then printf "\nUmgebung $ENVIRONMENT existiert und ist noch nicht sanitized. Sanitize data." printf "\n" # do sanitization here symfony ssh -p $PROJECT -e $ENVIRONMENT -- php bin/console app:sanitize-data printf "\nSanitizing data is finished, redeploying" add_env_var DATA_SANITIZED true $PROJECT $ENVIRONMENT else printf "\nEnvironment $ENVIRONMENT exists and does not need to be sanitized. Überspringen." fi elif [ "$ENVIRONMENT" == main ]; then printf "\nEnvironment $ENVIRONMENT is production one, skipping." else printf "\nEnvironment $ENVIRONMENT is not active $ENV_CHECK, skipping." fi done done }

Bitte beachten Sie: In diesem Skript setzen wir jedes Mal, wenn wir eine Umgebung bereinigen, die Umgebungsvariable DATA_SANITIZED, um sicherzustellen, dass das Skript bei der nächsten Ausführung die Umgebung nicht wiederholt bereinigt.

Je nachdem, auf welchem Rechner Sie das Skript ausführen wollen, passen Sie den Code bitte an Ihre Bedürfnisse an, aber er sollte in etwa so aussehen:

. fleet_sanitizer.sh # oder source fleet_sanitizer.sh ORGANIZATION=<organizationIdentifier> sanitize_organization_data $ORGANIZATION

Tipp: Sie finden die Organisationskennung für ein bestimmtes Projekt, indem Sie auf Ihren Namen und dann auf Einstellungen in der oberen rechten Ecke des Bildschirms klicken.

Und schon sind Ihre Daten bereinigt und Sie sind auf dem besten Weg zur GDPR-Konformität!

Wenn Sie weitere Fragen zu unseren Sicherheits- und Compliance-Funktionen haben oder Probleme mit den oben genannten Methoden und/oder Schritten auftreten, wenden Sie sich an unser Support-Team, das Ihnen gerne weiterhilft.

Bleiben Sie auf unseren Social Media- und Community-Kanälen immer auf dem Laufenden. Besuchen Sie uns auf Dev.to, Reddit und Discord.

Ihr größtes Werk
steht vor der Tür

Kostenloser Test
Discord
© 2025 Platform.sh. All rights reserved.