はじめに
最近PythonでAWSサービス関連操作をモックしてくれる「moto」というライブラリを使って開発をしているのですが、GuardDuty周りも一部対応しているようなので試してみました。
boto3を使っている方はテストを書く時とっても便利なので、以下の記事も合わせてご覧ください。
環境
以下の環境で実施してます。
環境 | バージョン |
---|---|
Python | 3.8.13 |
Poetry | 1.2.2 |
moto | 4.1.9 |
boto3 | 1.26.135 |
pytest | 7.3.1 |
やってみる
motoのドキュメントを確認したところ、create_detector
とget_detector
は対応しているようだったので、これらを使ってテストを書いてみます。(その他にいくつか対応しているものはあったので、こちらから確認してみてください。)
テスト対象コード
まずはGuardDutyを有効化する関数をテストしてみます。
以下のコードではDataSourcesとしてS3Logsを有効にしてGuardDutyを有効化しています。
main.py
import boto3
def create_guardduty_detector(client):
response = client.create_detector(
Enable=True,
DataSources={
'S3Logs': {
'Enable': True
}
}
)
detector_id = response['DetectorId']
return detector_id
テストコード
テストでmotoを利用するには、各AWSサービスに応じたモックを用意します。
motoからGuardDutyに対応するmock_guardduty
をインポートして、テスト対象の関数にデコレーター(@mock_guardduty
)として記述してください。
このテストコードでは、clientを取得し、テスト対象コード内のcreate_guardduty_detector
をモック内で実行します。この時、guarddutyのcreate_detector
が実行されますが、テストコード側で@mock_guardduty
を記述しているためAWS環境でGuardDutyが有効化されることはありません。
get_detector
を実行することで、モック内で有効化したGuardDutyのdetectorから情報を取得して出力してみます。
test_main.py
from moto import mock_guardduty
import main
import boto3
@mock_guardduty
def test_create_guardduty_detector_success():
client = boto3.client('guardduty', region_name='ap-northeast-1')
detector_id = main.create_guardduty_detector(client)
res = client.get_detector(DetectorId=detector_id)
print(res)
assert res["Status"] == "ENABLED"
# S3Logsが有効化されていることを確認
assert res["DataSources"]["S3Logs"]["Status"] == "ENABLED"
最後のassertでは、取得したdetectorの情報からDataSourcesのS3Logsが有効化されているか?をテストしています。
実行結果
テストを実行してみると、問題なくパスしました。 有効化したGuardDutyは、DataSourcesとしてS3Logsを有効されていることが確認できました。
.venv ❯ poetry run pytest -s
===================================================================== test session starts ======================================================================
platform darwin -- Python 3.8.13, pytest-7.3.1, pluggy-1.0.0
rootdir: /Users/suzuki.jun/Documents/moto-guardduty
collected 1 item
test_main.py {'ResponseMetadata': {'HTTPStatusCode': 200, 'HTTPHeaders': {}, 'RetryAttempts': 0}, 'CreatedAt': '2023-05-18T15:47:46.755182Z', 'FindingPublishingFrequency': 'SIX_HOURS', 'ServiceRole': 'arn:aws:iam::123456789012:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty', 'Status': 'ENABLED', 'UpdatedAt': '2023-05-18T15:47:46.755182Z', 'DataSources': {'CloudTrail': {'Status': 'DISABLED'}, 'DNSLogs': {'Status': 'DISABLED'}, 'FlowLogs': {'Status': 'DISABLED'}, 'S3Logs': {'Status': 'ENABLED'}, 'Kubernetes': {'AuditLogs': {'Status': 'DISABLED'}}}, 'Tags': {}}
.
====================================================================== 1 passed in 0.37s =======================================================================
print(res)
で出力しているdetectorの情報が見づらいので整形してみます。
有効化した際に指定したS3LogsがENABLEDになっていて、ServiceRoleなどのARNはサンプルのアカウントIDに置き換えられていますね。
{
"ResponseMetadata": {
"HTTPStatusCode": 200,
"HTTPHeaders": {},
"RetryAttempts": 0
},
"CreatedAt": "2023-05-18T15:45:31.213323Z",
"FindingPublishingFrequency": "SIX_HOURS",
"ServiceRole": "arn:aws:iam::123456789012:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty",
"Status": "ENABLED",
"UpdatedAt": "2023-05-18T15:45:31.213323Z",
"DataSources": {
"CloudTrail": {
"Status": "DISABLED"
},
"DNSLogs": {
"Status": "DISABLED"
},
"FlowLogs": {
"Status": "DISABLED"
},
"S3Logs": {
"Status": "ENABLED"
},
"Kubernetes": {
"AuditLogs": {
"Status": "DISABLED"
}
}
},
"Tags": {}
}
わざと失敗させてみる
テスト対象コードを以下のように、S3LogsをFalseに変更してみます。
main.py
import boto3
def create_guardduty_detector(client):
response = client.create_detector(
Enable=True,
DataSources={
'S3Logs': {
'Enable': False
}
}
)
detector_id = response['DetectorId']
return detector_id
この状態でテストを実行すると、想定通り失敗しました。
テストコード上のassertで、res["DataSources"]["S3Logs"]["Status"]
の値で期待していたのはENABLED
のはずが、実際は有効化されていないためDISABLED
になっていると怒られます。分かりやすいですね。
=========================================================================== FAILURES ===========================================================================
____________________________________________________________ test_create_guardduty_detector_success ____________________________________________________________
@mock_guardduty
def test_create_guardduty_detector_success():
client = boto3.client('guardduty', region_name='ap-northeast-1')
detector_id = main.create_guardduty_detector(client)
res = client.get_detector(DetectorId=detector_id)
print(res)
assert res["Status"] == "ENABLED"
> assert res["DataSources"]["S3Logs"]["Status"] == "ENABLED"
E AssertionError: assert 'DISABLED' == 'ENABLED'
E - ENABLED
E + DISABLED
test_main.py:14: AssertionError
=================================================================== short test summary info ====================================================================
FAILED test_main.py::test_create_guardduty_detector_success - AssertionError: assert 'DISABLED' == 'ENABLED'
====================================================================== 1 failed in 0.37s =======================================================================
まとめ
motoを使ったGuardDutyのテストを試してみました。テストコード側にインポートしたmotoのライブラリをデコレーターとして記述するだけなのでとても便利です。 AWS側のアップデートに追いついていない部分もあったりしますが、テストコードをサクッと書けるので是非利用してみてください。