Die Sicherheit und Vertraulichkeit sensibler Daten hat bei der Softwareentwicklung oberste Priorität. Anwendungsprotokolle, einer der häufigsten Vektoren für Datenlecks, werden sorgfältig geschützt, um das Vorhandensein von Geheimnissen zu verhindern. Die gleichen Bedenken und Risiken gelten auch für Testprotokolle, die Passwörter oder Zugriffstoken preisgeben können. Tools, die CI-Workflows ausführen, bieten normalerweise Mechanismen, um vertrauliche Daten in Protokollen mit geringem oder gar keinem Aufwand zu maskieren. Dies ist zwar sehr praktisch, effizient und einfach zu verwenden, in bestimmten Situationen kann dies jedoch nicht ausreichend sein.
Zum Beispiel leistet GitHub Actions gute Arbeit beim Umgang mit Geheimnissen. Jedes im Workflow definierte Geheimnis wird automatisch aus der erfassten Ausgabe maskiert, was wie ein Zauber wirkt. Allerdings hat es, wie jedes CI-System, seine Grenzen. Wenn der Ausgabebericht einen anderen Pfad annimmt – etwa indem er in einer Datei gespeichert, Junit generiert oder an einen Remote-Protokollspeicher gesendet wird –, ist GitHub Actions nicht in der Lage, den Inhalt zu überprüfen und die Geheimnisse zu maskieren.
Darüber hinaus werden Tests nicht immer innerhalb eines CI-Workflows ausgeführt, und selbst dann müssen möglicherweise noch Geheimnisse verborgen werden. Stellen Sie sich vor, Sie führen Tests lokal durch und geben ein Protokoll frei, um ein Problem zu besprechen. Ohne es zu merken, fügen Sie Ihrem Zugriffstoken eine URL hinzu.
Daher ist es auf allen Ebenen unerlässlich, über einen Mechanismus zum Umgang mit sensiblen Daten in Testprotokollen zu verfügen. Der beste Ansatz besteht darin, dies direkt auf Testebene oder innerhalb des Testframeworks selbst zu implementieren. Dadurch wird sichergestellt, dass keine Geheimnisse aus der Primärquelle durchsickern und verhindert wird, dass sie durch das System weitergegeben werden.
Die Maskierung von Geheimnissen direkt in Tests aufrechtzuerhalten, kann relativ kostspielig und fehleranfällig sein und fühlt sich oft wie ein verlorener Kampf an. Stellen Sie sich beispielsweise vor, Sie müssten eine URL mit einem Token als Parameter entwerfen. Diese URL muss für die Verwendung in einer Anfrage anders gerendert werden als im Protokoll.
Im Gegensatz dazu bietet das Abfangen der Berichtserstellung innerhalb des Test-Frameworks eine ideale Gelegenheit, sich in den Prozess einzumischen und die Datensätze zu ändern, um sensible Daten zu eliminieren. Dieser Ansatz ist für die Tests transparent, erfordert keine Änderungen am Testcode und funktioniert genau wie die Geheimnismaskierungsfunktion in CI-Workflows – führen Sie ihn einfach aus und vergessen Sie die Verwaltung der Geheimnisse. Es automatisiert den Prozess und stellt sicher, dass sensible Daten geschützt sind, ohne den Testaufbau zusätzlich zu komplexieren.
Das ist genau das, was pytest-mask-secrets tut, offensichtlich, wenn pytest zur Testausführung verwendet wird. Unter seinen vielen Funktionen bietet Pytest ein umfangreiches und flexibles Plugin-System. Zu diesem Zweck können Sie direkt vor der Protokollgenerierung in den Prozess einsteigen, also zu einem Zeitpunkt, an dem bereits alle Daten erfasst wurden. Dies erleichtert die Suche nach sensiblen Werten und deren Entfernung aus den Datensätzen, bevor diese ausgegeben werden.
Um zu veranschaulichen, wie das funktioniert, ist ein einfaches Beispiel am effektivsten. Unten finden Sie einen trivialen Test, der möglicherweise kein reales Testszenario darstellt, aber dazu dient, die Geheimnisse von Pytest-Masken recht gut zu demonstrieren.
import logging import os def test_password_length(): password = os.environ["PASSWORD"] logging.info("Tested password: %s", password) assert len(password) > 18
In diesem Beispiel gibt es eine Behauptung, die möglicherweise fehlschlägt (und das wird sie auch), zusammen mit einer Protokollnachricht, die ein Geheimnis enthält. Ja, es mag albern erscheinen, ein Geheimnis in das Protokoll aufzunehmen, aber stellen Sie sich ein Szenario vor, in dem Sie eine URL mit einem Token als Parameter haben und eine detaillierte Debug-Protokollierung aktiviert ist. In solchen Fällen könnten Bibliotheken wie Anfragen das Geheimnis versehentlich auf diese Weise protokollieren.
Jetzt zum Testen. Legen Sie zunächst das zu Testzwecken benötigte Geheimnis fest:
(venv) $ export PASSWORD="TOP-SECRET"
Als nächstes führen Sie den Test durch:
(venv) $ pytest --log-level=info test.py ============================= test session starts ============================== platform linux -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 rootdir: /tmp/tmp.AvZtz7nHZS collected 1 item test.py F [100%] =================================== FAILURES =================================== _____________________________ test_password_length _____________________________ def test_password_length(): password = os.environ["PASSWORD"] logging.info("Tested password: %s", password) > assert len(password) > 18 E AssertionError: assert 10 > 18 E + where 10 = len('TOP-SECRET') test.py:8: AssertionError ------------------------------ Captured log call ------------------------------- INFO root:test.py:7 Tested password: TOP-SECRET =========================== short test summary info ============================ FAILED test.py::test_password_length - AssertionError: assert 10 > 18 ============================== 1 failed in 0.03s ===============================
Standardmäßig erscheint der geheime Wert zweimal in der Ausgabe: einmal in der erfassten Protokollnachricht und erneut in der fehlgeschlagenen Behauptung.
Aber was ist, wenn pytest-mask-secrets installiert ist?
(venv) $ pip install pytest-mask-secrets
Und entsprechend konfiguriert. Es muss eine Liste der Umgebungsvariablen kennen, die die Geheimnisse enthalten. Dies geschieht durch Setzen der Variablen MASK_SECRETS:
(venv) $ export MASK_SECRETS=PASSWORD
Jetzt führen Sie den Test noch einmal aus:
(venv) $ pytest --log-level=info test.py ============================= test session starts ============================== platform linux -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 rootdir: /tmp/tmp.AvZtz7nHZS plugins: mask-secrets-1.2.0 collected 1 item test.py F [100%] =================================== FAILURES =================================== _____________________________ test_password_length _____________________________ def test_password_length(): password = os.environ["PASSWORD"] logging.info("Tested password: %s", password) > assert len(password) > 18 E AssertionError: assert 10 > 18 E + where 10 = len('*****') test.py:8: AssertionError ------------------------------ Captured log call ------------------------------- INFO root:test.py:7 Tested password: ***** =========================== short test summary info ============================ FAILED test.py::test_password_length - AssertionError: assert 10 > 18 ============================== 1 failed in 0.02s ===============================
Anstelle des Geheimwerts erscheinen jetzt Sternchen überall dort, wo das Geheimnis gedruckt worden wäre. Die Arbeit ist erledigt und der Prüfbericht ist nun frei von sensiblen Daten.
D'après l'exemple, il peut sembler que pytest-mask-secrets ne fait pas grand-chose de plus que ce que GitHub Actions fait déjà par défaut, ce qui rend l'effort redondant. Cependant, comme mentionné précédemment, les outils d'exécution de flux de travail CI masquent uniquement les secrets dans la sortie capturée, laissant les fichiers JUnit et autres rapports inchangés. Sans pytest-mask-secrets, des données sensibles pourraient toujours être exposées dans ces fichiers : cela s'applique à tout rapport généré par pytest. D'un autre côté, pytest-mask-secrets ne masque pas la sortie directe lorsque l'option log_cli est utilisée, les fonctionnalités de masquage des workflows CI sont donc toujours utiles. Il est souvent préférable d’utiliser les deux outils conjointement pour assurer la protection des données sensibles.
Ça y est. Merci d'avoir pris le temps de lire cet article. J'espère que cela a fourni des informations précieuses sur l'utilisation de pytest-mask-secrets pour améliorer la sécurité de votre processus de test.
Bon test !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!