ホームページ  >  記事  >  バックエンド開発  >  pytest-mask-secrets を使用してテスト シークレットを保護する

pytest-mask-secrets を使用してテスト シークレットを保護する

WBOY
WBOYオリジナル
2024-08-12 18:43:59565ブラウズ

Securing Testing Secrets with pytest-mask-secrets

機密データを安全かつプライベートに保つことは、ソフトウェア開発における最優先事項です。一般的な漏洩ベクトルの 1 つであるアプリケーション ログは、機密情報の存在を防ぐために慎重に保護されています。同じ懸念とリスクは、パスワードやアクセス トークンが明らかになる可能性があるテスト ログにも当てはまります。 CI ワークフローを実行するツールは通常、ログ内の機密データをほとんど、またはまったく手間をかけずにマスクするメカニズムを提供します。これは非常に便利で効率的で使いやすいですが、状況によっては十分ではない場合があります。

CI ワークフローのマスキングだけでは不十分な理由

たとえば、GitHub Actions はシークレットの処理に優れています。ワークフロー内で定義されたシークレットは、キャプチャされた出力から自動的にマスクされ、魔法のように機能します。ただし、他の CI システムと同様に、これにも制限があります。出力レポートが別のパスをとる場合 (ファイルに保存される場合、junit が生成される場合、またはリモート ログ ストアに送信される場合など)、GitHub Actions にはコンテンツを検査してシークレットをマスクする機能がありません。

さらに、テストは常に CI ワークフロー内で実行されるわけではなく、その場合でもシークレットを隠す必要がある場合があります。ローカルでテストを実行し、ログを共有して問題について話し合っていると想像してください。気づかないうちに、アクセス トークンに URL を含めてしまいます。

したがって、テスト ログ内の機密データを処理するメカニズムを備えることは、あらゆるレベルで不可欠です。最良のアプローチは、これをテスト レベルで直接実装するか、テスト フレームワーク自体内で実装することです。これにより、機密情報が一次情報源から漏洩することがなくなり、機密情報がシステムを介して渡されることが防止されます。

適切なレベルで保護を追加する

テストで直接シークレットのマスキングを維持することは、比較的コストがかかり、エラーが発生しやすく、しばしば負け戦のように感じられます。たとえば、パラメータとしてトークンを使用して URL を設計する必要があると想像してください。この URL は、リクエストで使用する場合と、ログに存在する場合とは異なる方法でレンダリングする必要があります。

対照的に、テスト フレームワーク内でレポート生成を傍受すると、プロセスに侵入してレコードを変更して機密データを削除する理想的な機会が得られます。このアプローチはテストに対して透過的であり、テスト コードを変更する必要がなく、CI ワークフローのシークレット マスキング機能と同様に機能します。実行するだけで、シークレットの管理は忘れられます。プロセスを自動化し、テスト設定を複雑にすることなく機密データを確実に保護します。

これは、明らかに pytest がテストの実行に使用されている場合に、pytest-mask-secrets が行うこととまったく同じです。 pytest は、その多くの機能の中でも、豊富で柔軟なプラグイン システムを提供します。この目的のために、ログが生成される直前、すべてのデータがすでに収集されている時点でプロセスにフックできるようになります。これにより、機密値を出力する前にレコードから簡単に検索して削除できます。

テストしてみる: 実践的なデモ

これがどのように機能するかを説明するには、簡単な例が最も効果的です。以下は、実際のテスト シナリオを表していないかもしれないが、pytest-mask-secrets を十分に実証するという目的を十分に果たしている簡単なテストです。

import logging
import os


def test_password_length():
    password = os.environ["PASSWORD"]
    logging.info("Tested password: %s", password)
    assert len(password) > 18

この例には、失敗する可能性のあるアサーションと、シークレットを含むログ メッセージが含まれています。確かに、ログにシークレットを含めるのは愚かに思えるかもしれませんが、パラメーターとしてトークンを含む URL があり、詳細なデバッグ ログが有効になっているシナリオを考えてみましょう。このような場合、リクエストなどのライブラリがこの方法で誤ってシークレットを記録する可能性があります。

さあ、テストです。まず、テスト目的に必要なシークレットを設定します:

(venv) $ export PASSWORD="TOP-SECRET"

次に、テストを実行します:

(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 ===============================

デフォルトでは、シークレット値は出力に 2 回表示されます。1 回目はキャプチャされたログ メッセージに、もう 1 回目は失敗したアサーションに表示されます。

しかし、pytest-mask-secrets がインストールされている場合はどうなるでしょうか?

(venv) $ pip install pytest-mask-secrets

それに応じて設定しました。秘密を保持する環境変数のリストを知る必要があります。これを行うには、MASK_SECRETS 変数を設定します:

(venv) $ export MASK_SECRETS=PASSWORD

次に、テストを再実行します。

(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 ===============================

今度は、シークレットが出力されるはずの場所に、シークレット値の代わりにアスタリスクが表示されます。作業は完了し、テスト レポートには機密データが含まれなくなりました。

最後に

この例から、pytest-mask-secrets は GitHub Actions がデフォルトですでに行っていること以上のことは行わず、その作業が冗長であるように見えるかもしれません。ただし、前述したように、CI ワークフロー実行ツールはキャプチャされた出力のシークレットのみをマスクし、JUnit ファイルやその他のレポートは変更されません。 pytest-mask-secrets がないと、機密データがこれらのファイルに公開される可能性があります。これは、pytest によって生成されるすべてのレポートに当てはまります。一方、pytest-mask-secrets は、log_cli オプションが使用されている場合は直接出力をマスクしないため、CI ワークフローのマスク機能は引き続き役立ちます。多くの場合、機密データを確実に保護するには、両方のツールを組み合わせて使用​​することが最善です。

これです。この投稿をお読みいただきありがとうございます。 pytest-mask-secrets を使用してテスト プロセスのセキュリティを強化することについて、貴重な洞察が得られれば幸いです。

テストをお楽しみください!

以上がpytest-mask-secrets を使用してテスト シークレットを保護するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。