


Ansibleのインフラ自動化を、セキュリティを弱めずに作りレビューする
AIにAnsibleのplaybookなどのインフラ自動化を書かせる・直させる時に使う。lintとセキュリティ検査を警告ゼロで通し、パスワードや鍵をリポジトリに置かず、ネットワークは下の層から調べ、テストがバグを見つけられる事を確かめ、実際に行った検査だけを報告させる。
このAIルールを使うには
## 安定とセキュリティを優先し、頼まれた事だけを変える
インフラ自動化をセキュリティ上重要なコードとして扱う。小さな設定ミスでも、プライバシー、アクセス制御、ルーティング、サービス可用性を気づかれないまま弱める可能性がある。
- 機能数より安定性、利便性よりセキュリティ、巧妙さより明確さを優先する。
- 依頼された問題だけを正確に解決する。無関係な修正や推測による機能は別の作業に分ける。
- 変更完了を宣言する前に、影響する自動化を実行して仮定を検証する。
- 新しい依存関係を安易に追加しない。依存関係はそれぞれ攻撃対象領域と保守負担を広げる。
- 新しい抽象化や設定オプションを導入する前に、既存のリポジトリパターンに従う。
- 新しいオプションはそれぞれ追加のテスト範囲として扱い、利用者に実証済みの必要性がある場合に限って追加する。
- 保持された状態が欠陥を隠す可能性がある場合は、動作テストに新規デプロイを使う。
## lintとセキュリティ検査を、警告ゼロで通す
レビュー前にリポジトリの完全なローカル品質ゲートを実行する。品質ゲートには、設定lint、YAML lint、言語lint、shell解析、静的セキュリティ解析、playbook構文検査、関連する単体テストを含める。
継続的インテグレーションでは警告を1件も許容しない。セキュリティ上重要な自動化では、外見上の問題に見える指摘が、壊れたtemplate、安全でないfile permission、繰り返されるノイズに埋もれた実際の回帰を示す場合がある。
指摘は次の順序で解決する。
1. 根本の問題を修正する。
2. そのルールがリポジトリに適さない場合は、該当ツールの設定でallowlistに登録し、理由を説明する。
3. inline suppressionは最後の手段に限り、正確なルール名と例外の根拠を記録する。
指摘を恒久的な警告一覧へ移してはならない。ルールは合格させるか、将来も確認できる根拠を添えて明示的にskipする。
一般的な必須検査には次を含める。
- YAML文書を`---`で開始し、parserが必要とする場合は曖昧なworkflow keyを引用符で囲む。
- 広範な`ignore_errors`より、`failed_when`などの明示的な失敗条件を優先する。
- Jinja2のspacingを一貫させる。
- `umask`に依存せず、すべてのfileまたはdirectory taskで`mode`を指定する。
- shell変数を引用符で囲み、厳格なshell error handlingを有効にする。
- 静的解析の抑制は近道ではなく、レビュー対象の例外として扱う。
変更を提出する前に差分を確認し、実行した検査を記録し、継続的インテグレーションが同じ必須検証を実行することを確認する。
## 入力を早めに確かめ、パスワードや鍵をリポジトリに置かない
高コストな処理や状態変更の前に入力を検証する。空のcollection、欠けた設定、不正なaddress、互換性のないversion、permissionの不一致を、信頼できる最も早い時点で拒否する。
- fileとdirectoryのmodeを明示的に設定する。
- permission、authentication、cryptography、security boundaryのエラーでは失敗させる。警告は本当に任意の動作に限る。
- 失敗した条件と安全な修正方法を示し、対処可能なエラーにする。
- 最小権限を適用し、permission、account、保持credential、process capabilityを最小化する。
- 安全な既定値、強力なcryptography、厳格なfirewall rule、最小限のlogging、最小限のdata retentionを使う。
- password、private key、access token、provider credentialをsource controlへ絶対に置かない。
- 確認済みのsecurity vulnerabilityには速やかに対応し、更新後の動作を検証する。
- 文書化されていないworkaroundを避ける。workaroundが存在する理由を記録し、上流issueを参照して、保守担当者が後で安全に取り扱いを終えられるようにする。
task、conditional、provider path、moduleを追加する前に、確立されたpatternをcodebase内で検索する。適さない証拠がない限り、既存のcontrol flowを再利用する。
## Ansibleのloopと、Jinja2テンプレートのよくある間違いを防ぐ
従来のiterationを機械的に`loop`へ置き換えてはならない。一部の従来形式は入れ子のlistをflattenするが、`loop`はその構造を保持する。結果のitem shapeを検証し、想定したfileやresourceが作成されることを確認する。
- 既存のlistを別のlistで包まず、`loop`へ直接渡す。
- 以前の構文が複数inputをflattenしていた場合は、listを明示的に結合する。
- path変数の末尾にslashを付けず、separatorの重複を防ぐ。
- 読み取り専用commandとhandlerには`changed_when: false`を設定し、state reportを正確に保つ。
- commandの失敗が明示的に想定され、安全な場合に限って`failed_when: false`を使う。無関係な失敗を隠してはならない。
Jinja2のnative evaluationでは次を守る。
- 条件には`"true"`や`"false"`などのstringではなく、実際のbooleanが必要となる。
- lookupへ変数を直接渡し、template expressionの中へ別のtemplate expressionを入れ子にしない。
- lookupがfile contentを返す場合は、JSON textを明示的にparseする。
- `default()`はfalsy value用optionを有効にしない限り、空stringを置き換えないことを理解する。
- templateで生成する複雑なlist comprehensionを、通常のautomation loopと明示的な蓄積へ置き換える。
- boolean predicateにはtestを使い、data transformationにはfilterを使う。
automation frameworkの実際のfilterまたは忠実なmockを使ってtemplateをテストする。構文が有効なだけではnative modeの動作を証明できない。
## ネットワークサービスの不具合を、待ち受けポートから順に調べる
すべての接続症状をrouting failureと決めつけず、不確かな最下層からnetwork automationをdebugする。
1. 想定したTCP socketとUDP socketが、意図したaddressとportでlistenしていることを確認する。
2. 設定警告がないかserviceとsocket unitのstatusを確認する。
3. 必要なkernel network settingを検証する。
4. 設定済みresolverに対してname resolutionを直接テストする。
5. その後に限ってfirewall、address translation、policy routingのruleを調べる。
socket activationを使うserviceでは、service設定だけでなくsocket unitを設定する。必要な場合は継承したlistenerをclearし、streamとdatagramの両listenerを定義し、変更後にsocketをrestartする。socketをdisableするとservice dependencyを壊す可能性がある。
platform差を意図的に考慮する。
- firewall backendはruleを並べ替える可能性がある。automationが想定する動作のbackendを固定するか検証する。
- 複数network接続を持つhostでは、address translation用の明示的なoutbound interfaceが必要な場合がある。
- 要件を満たす場合は、不要に複雑なsource translationより、interfaceを明示した単純なmasqueradingを優先する。
- インストール済みtool versionを検出した後、条件付きでcompatibility flagを追加する。
- hostとportの構文が必要とする箇所では、IPv6 endpointをbracketで囲む。
- routingやtranslation ruleを再設計する前にname resolutionを確認する。
network ruleを単純に保ち、task resultから成功を推測せずpacket flowをテストする。
## 回帰テストが、バグを戻すと本当に失敗するか確かめる
回帰テストは、防止すると主張する欠陥を実際に検出できることを示さなければならない。
1. 失敗条件に焦点を絞ったテストを書く。
2. 一時的に欠陥を導入するか、関連条件を変異させる。
3. 新しいテストが意図した理由で失敗することを確認する。
4. 正しい実装へ戻し、テストが合格することを確認する。
5. テストが防ぐ正確な回帰を文書化する。
動作がtemplate、file path、permission、firewall rule、collection shapeに依存する場合は、生成textだけでなく観測可能な結果をテストする。状況に応じて、作成されたfile、有効なmode、parse済みvalue、listener state、packet flow、render結果を検証する。
変更によってpathや動作が変わる場合は既存テストを更新する。失敗を検出する能力が実証されるまで、合格中のテストに意味があると仮定してはならない。
## 質問で止まらないデプロイにし、実際に行った検査だけを報告する
継続的インテグレーションやagent向けのautomationは、完全に非対話で実行できる経路に対応しなければならない。
- 承認済みのparameterまたはenvironment variableで対応する変数が事前定義されている場合は、すべてのpromptを迂回する。
- 中核変数の完全な一覧、type、default、purposeを文書化する。
- provider固有credentialを通常設定から分離し、承認済みのsecret channelからだけ受け取る。
- localまたは影響の大きいdeployment modeでは、明示的なconfirmation variableを必須にする。
- headless runがinput不足で停止しないよう、optional featureへ明示的なboolean valueを与える。
- 代表的な複数interface・複数address条件を備えたclean environmentで非対話経路をテストする。
完了前に、すべてのlinter、syntax check、関連するunit test、新規deploymentまたは同等のintegration testを実行する。無関係な修正、根拠のないdependency、隠れたsuppression、file mode不足、secret、文書化されていない動作がないか差分をreviewする。収集した正確な証拠を報告し、静的検査しか実行していない場合はdeployment successを主張しない。
