prowler-attack-paths-query について
prowler-attack-paths-queryは、Cartographyのクラウドグラフを対象とするProwler Attack Pathsクエリ作成用の開発スキルです。権限昇格チェーンやネットワーク公開などのリスク検出に向け、クエリ構造、互換性、性能の規約を示します。
主な機能
- 01定義済み・カスタムクエリの説明
- 02openCypherクエリパターン
- 03権限昇格とネットワーク公開の例
- 04直積を避ける方法
- 05リスト属性のグラフ表現
活用シーン
- 01クラウドグラフ向け攻撃パスクエリの追加
- 02権限昇格検出クエリの作成
- 03性能問題を避けるクエリの改善
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ prowler-cloud/ prowler --skill prowler-attack-paths-queryインストール後、Claude Code で/prowler-attack-paths-queryを入力して読み込まれたか確認します。
詳細
概要
Attack Paths クエリは、Cartography が取り込んだクラウドグラフに対する読み取り専用の openCypher クエリです。権限昇格の連鎖、ネットワークの露出、その他のグラフ構造を持つセキュリティリスクを検出します。クエリは openCypher Version 9 で記述されているため、Neo4j と Amazon Neptune の両方のシンクで実行できます。
このスキルは、クエリの作成に役立つ簡潔で実践的なリファレンスです。人間が読みやすい完全版のリファレンス(グラフモデル、リスト型および JSON エンコードされたプロパティ、互換性、具体例)については、docs/developer-guide/attack-paths-queries.mdx を参照してください。
2 種類のクエリ利用者
| 定義済みクエリ | カスタムクエリ | |
|---|---|---|
| 保存場所 | api/src/backend/api/attack_paths/queries/{provider}.py | カスタムクエリ API エンドポイント経由でユーザーが提供 |
| プロバイダーの分離 | AWSAccount {id: $provider_uid} を起点とし、パスの接続性で分離 | cypher_sanitizer.py による _Provider_{uuid} ラベルの自動挿入 |
| 記述方法 | すべての MATCH を aws 変数からつなげる | 分離用の定型記述を含まないプレーンな Cypher |
| 内部ラベル | 使用しない | 使用しない(システムが挿入) |
定義済みクエリ:すべてのノードは、グラフのトラバーサルを通じて AWSAccount ルートから到達できる必要があります。これが分離境界です。
カスタムクエリ:自然な Cypher を記述してください。実行処理が各ノードパターンに _Provider_{uuid} ラベルを挿入し、クエリ後のフィルターがエッジケースに対応します。
入力ソース
新しいクエリの入力ソースは 2 つあります。
-
pathfinding.cloud ID(例:
ECS-001、GLUE-001)。Datadog のリサーチカタログです。集約されたpaths.jsonは WebFetch で扱うには大きすぎます。# Fetch a single path by ID curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq '.[] | select(.id == "ecs-002")' # List all path IDs and names curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq -r '.[] | "\(.id): \(.name)"' # Filter by service prefix curl -s https://raw.githubusercontent.com/DataDog/pathfinding.cloud/main/docs/paths.json \ | jq -r '.[] | select(.id | startswith("ecs")) | "\(.id): \(.name)"'jqが利用できない場合は、python3 -c "import json,sys; ..."を使用してください。 -
依頼者による自然言語の説明。
クエリの構造
プロバイダーのスコープ設定パラメーター
| パラメーター | プロパティ | 使用対象 | 目的 |
|---|---|---|---|
$provider_uid | id | AWSAccount | クエリを特定のアカウントに限定します |
実行処理は $provider_uid を自動的にバインドします。その他すべてのノードは、AWSAccount を起点とするパスの接続性によって分離されます。
インポート
from api.attack_paths.queries.types import (
AttackPathsQueryAttribution,
AttackPathsQueryDefinition,
AttackPathsQueryParameterDefinition,
)
from tasks.jobs.attack_paths.config import PROWLER_FINDING_LABEL
PROWLER_FINDING_LABEL は常に f-string 補間を使って指定し、"ProwlerFinding" を直接ハードコードしないでください。
定義フィールド
- id:ケバブケースの
{provider}-{description}。例:aws-ec2-privesc-passrole-iam。 - name:短く、人にわかりやすいラベルです。ソースから作成したクエリには参照 ID を追加します。例:
"EC2 Instance Launch with Privileged Role (EC2-001)"。 - short_description:技術的な権限には触れない、1 文の説明です。
- description:技術的な詳細をすべて含む、プレーンテキストの説明です。
- provider:
aws、azure、gcp、kubernetes、またはgithub。 - cypher:f-string の Cypher 本文です。リテラルの
{/}は{{/}}としてエスケープします。 - parameters:存在しない場合は
parameters=[]にします。 - attribution:ソースから作成したクエリでは、任意で
AttackPathsQueryAttribution(text, link)を指定します。linkには小文字の ID を使用します。
定数をプロバイダーファイルの末尾にある {PROVIDER}_QUERIES リストに追加します。
定義済みクエリテンプレート
標準形では、プリンシパルのパス、任意のターゲットパス、重複排除されたノード、型付きの検出結果オーバーレイを組み合わせます。
AWS_{QUERY_NAME} = AttackPathsQueryDefinition(
id="aws-{kebab-case-name}",
name="{Label} ({REFERENCE_ID})",
short_description="{One sentence.}",
description="{Full technical explanation.}",
attribution=AttackPathsQueryAttribution(
text="pathfinding.cloud - {REFERENCE_ID} - {permission}",
link="https://pathfinding.cloud/paths/{reference_id_lowercase}",
),
provider="aws",
cypher=f"""
// Find principals with {permission}
MATCH path_principal = (aws:AWSAccount {{id: $provider_uid}})--(principal:AWSPrincipal)-[:POLICY]->(policy:AWSPolicy)-[:STATEMENT]->(stmt:AWSPolicyStatement {{effect: 'Allow'}})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['{permission_lowercase}', '{service}:*']
OR act.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
// Pre-aggregate the statement's resource values (see "Avoiding cartesian products")
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, principal, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, principal, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Target policies attached to the principal, matched once against the resource list
MATCH path_target = (aws)--(target_policy:AWSPolicy)--(principal)
WITH path_principal, path_target, res_values, res_wildcard, target_policy.arn AS parn
WHERE parn CONTAINS $provider_uid
AND (res_wildcard OR size([rv IN res_values WHERE parn CONTAINS rv]) > 0)
WITH DISTINCT path_principal, path_target
WITH collect(path_principal) + collect(path_target) AS paths
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
""",
parameters=[],
)
要点:
- プリンシパルのパスでは、
POLICYとSTATEMENTのホップを型指定します。どちらもファンアウトが小さい(各プリンシパルに数個のポリシーがあり、各ポリシーに数個のステートメントがあります)ため、型付きエッジを使うと、プランナーは低コストのインラインフィルターとして評価できます。 (aws)--のハブへのホップは匿名のままにします。AWSAccountは次数の高いノードで、アカウント内のすべてのプリンシパル、ロール、ポリシー、リソースへとファンアウトします。これらのエッジを型指定すると、プランナーはハブから列挙する必要が生じ、マルチテナントの Neptune ではパフォーマンスが低下します。- その他のリレーションシップ型は、ファイル内の既存のクエリですでに使われている箇所に限って使用します(
TRUSTS_AWS_PRINCIPAL、STS_ASSUMEROLE_ALLOW、MEMBER_AWS_GROUP、HAS_EXECUTION_ROLE)。 - 検出結果のプローブは
:HAS_FINDINGと型指定し、方向を指定しません。この型により、Neptune はインラインのエッジフィルターを適用できます。また、方向を指定しないことで、ファイルの他の部分の規約に合わせます。 - 権限ゲートごとに
WITH DISTINCTを使って重複行をまとめ、後続の節で必要な変数だけを引き継ぎます。 - 各
HAS_*トラバーサルは、子アイテムノードに対するWHEREを含む独立したMATCH節にします。collect(path...)の前にWITH DISTINCT path_principal, path_targetを置き、結合によって生じる行の増加を重複排除します。 RETURNの形paths, dpf, dpfrは、シリアライザーとビジュアライザーが依存する契約です。変更しないでください。
直積の回避
ターゲットセット(AWSRole、AWSUser、AWSGroup)にマッチした後、別個の、接続されていない MATCH で各ターゲットをステートメントの HAS_RESOURCE アイテムと照合すると、直積が生成されます。フィルターの実行前に、すべてのターゲットがすべてのリソースアイテムと組み合わされます。多数のプリンシパルが存在するアカウントでは、エラーまたはタイムアウトが発生します。リソース値を先にリストへ集約してから、各ターゲットを一度だけマッチさせます。
// Pre-aggregate the statement's resource values into a list
MATCH (stmt)-[:HAS_RESOURCE]->(res:AWSPolicyStatementResourceItem)
WITH aws, path_principal, collect(DISTINCT res.value) AS res_values
WITH aws, path_principal, res_values, ('*' IN res_values) AS res_wildcard
// Match each target once; bind name/arn to locals so the predicate reads them once
MATCH path_target = (aws)--(target_role:AWSRole)
WITH path_principal, path_target, res_values, res_wildcard,
target_role.name AS rname, target_role.arn AS rarn
WHERE res_wildcard
OR size([rv IN res_values WHERE rv CONTAINS rname OR rarn CONTAINS rv]) > 0
- ターゲットのマッチング前にリソースを集約します。コストは
targets × resourcesではなくtargets + resourcesになります。これは純粋な書き換えであり、結果セットは同一です。 ('*' IN res_values)により、ワイルドカードによる許可を短絡評価し、必要な場合にのみリストを走査します。target.name/target.arnをローカル変数に束縛することで、リスト内包表記は各リソース値につき一度ではなく、ターゲットごとに一度だけ読み取ります。size([...]) > 0はany()に相当する、Neptune 互換の形式です(「openCypher の互換性」を参照)。- 2 ステートメントのクエリでは、各ステートメントのリソースを個別のリスト(
res_values、res2_values)に集約し、2 つのsize([...]) > 0チェックをANDで組み合わせます。 - リレーションシップ(
STS_ASSUMEROLE_ALLOW、TRUSTS_AWS_PRINCIPAL)によってすでに制約されているターゲットには、集約は不要です。リレーションシップによってセットがすでに限定されています。
権限昇格のサブパターン
一般的な攻撃タイプは、4 つの path_target の形状でカバーできます。どのパターンも、正規テンプレートの path_principal、重複排除用の末尾部分、および RETURN を共有します。異なるのは path_target の MATCH とそのリソース述語だけです。
| サブパターン | ターゲット | path_target の形状 | 例 |
|---|---|---|---|
| 自己昇格 | プリンシパル自身のポリシー | (aws)--(target_policy:AWSPolicy)--(principal) | IAM-001 |
| ユーザーへの水平移動 | 他の IAM ユーザー | (aws)--(target_user:AWSUser) | IAM-002 |
| Assume-role による水平移動 | AssumeRole 可能なロール | (aws)--(target_role:AWSRole)-[:STS_ASSUMEROLE_ALLOW]-(principal) | IAM-014 |
| PassRole + サービス | サービスを信頼するロール | (aws)--(target_role:AWSRole)-[:TRUSTS_AWS_PRINCIPAL]-(:AWSPrincipal {arn: '{service}.amazonaws.com'}) | EC2-001 |
複数権限クエリ(例:PassRole とサービス作成アクションの組み合わせ)では、path_target の前に権限ゲートを追加します。新しい変数(act2、policy2、stmt2)にはクエリごとのカウンターを再利用し、各ゲートの後で行を集約します。
MATCH (principal)-[:POLICY]->(policy2:AWSPolicy)-[:STATEMENT]->(stmt2:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt2)-[:HAS_ACTION]->(act2:AWSPolicyStatementActionItem)
WHERE toLower(act2.value) IN ['service:*', 'service:createsomething']
OR act2.value = '*'
WITH DISTINCT aws, principal, stmt, stmt2, path_principal
権限が、ステートメントのリソースを後で確認しない存在確認のみのゲートである場合は、ポリシーとステートメントを匿名にし、必要な変数のみを引き継ぎます。
MATCH (principal)-[:POLICY]->(:AWSPolicy)-[:STATEMENT]->(:AWSPolicyStatement {effect: 'Allow'})-[:HAS_ACTION]->(act3:AWSPolicyStatementActionItem)
WHERE toLower(act3.value) IN ['service:*', 'service:othersomething']
OR act3.value = '*'
WITH DISTINCT aws, principal, stmt, path_principal
一致するすべてのプリンシパルが同一の独立したリソースセットをターゲットにできる場合は、プリンシパルとターゲットのペアごとに 1 行作るのではなく、ターゲットを展開する前にプリンシパルのパスを収集します。
WITH aws, collect(DISTINCT path_principal) AS principal_paths
MATCH path_target = (aws)--(target)
WITH principal_paths, collect(DISTINCT path_target) AS target_paths
WITH principal_paths + target_paths AS paths
ターゲットを制約するステートメントは、引き続き HAS_RESOURCE のトラバーサル(res、res2)で確認します。aws.py の IAM-015 または EC2-001 を参照してください。
ネットワーク公開パターン
インターネットノードには、独立した検索対象としてではなく、スコープがすでに定まったリソースを経由して CAN_ACCESS で到達します。
cypher=f"""
// Resource scoped through the account anchor
MATCH path = (aws:AWSAccount {{id: $provider_uid}})--(resource:EC2Instance)
WHERE resource.exposed_internet = true
// Internet node reached via path connectivity through the resource
OPTIONAL MATCH (internet:Internet)-[can_access:CAN_ACCESS]->(resource)
WITH collect(path) AS paths, head(collect(internet)) AS internet, collect(can_access) AS can_access
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, internet, can_access, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
internet, can_access
"""
CAN_ACCESS エッジは、型付きかつ有向のままにします(-[:CAN_ACCESS]->)。これが同期時の正規の向きです。
リスト型プロパティを子ノードとして表現する
一部の Cartography ノードプロパティは、値のリストを保持します。たとえば、AWSPolicyStatement.action、AWSPolicyStatement.resource、KMSKey.encryption_algorithms、CloudFrontDistribution.aliases などがあります。グラフでは、それぞれのプロパティを、型付きエッジで親に接続された子アイテムノードの集合としてモデル化します。クエリではエッジをたどって値に到達します。親がリストを単一のフィールドとして保持することはありません。
命名規則
リスト型の親プロパティに対して、シンクには以下が格納されます。
- 子ラベル:
<ParentLabel><PropertyPascal>Item。例:AWSPolicyStatement.resource→AWSPolicyStatementResourceItem。 - エッジタイプ:
HAS_<PROPERTY_UPPER>。例:resource→HAS_RESOURCE。 - 子プロパティ:スカラーリストプロパティの場合は
value(単一のスカラー文字列)です。辞書のリスト型プロパティの場合(まれなケース。例:SecretsManagerSecretVersion.tags)は、カタログのfield_mapに従って、子が辞書のキーを名前付きフィールドとして保持します。
子アイテムのマッチにおける変数の命名
aws.py では、HAS_* トラバーサルごとにクエリ単位のカウンターを使い、連続するマッチで名前が重複しないようにします。
| エッジ | 1 番目 | 2 番目 | 3 番目 |
|---|---|---|---|
HAS_ACTION | act | act2 | act3 |
HAS_RESOURCE | res | res2 | res3 |
HAS_NOTACTION | nact | nact2 | nact3 |
HAS_NOTRESOURCE | nres | nres2 | nres3 |
カウンターは、各クエリの先頭でリセットされます。
例 - アクションの照合
iam:PassRole、iam:*、または * を許可するステートメントを検索します。独立した MATCH 句で HAS_ACTION エッジをたどり、付随する WHERE で述語を適用します。
MATCH (stmt:AWSPolicyStatement {effect: 'Allow'})
MATCH (stmt)-[:HAS_ACTION]->(act:AWSPolicyStatementActionItem)
WHERE toLower(act.value) IN ['iam:passrole', 'iam:*']
OR act.value = '*'
リテラルアクションのリストでは、toLower(act.value) を使って大文字と小文字を統一します。IAM の作成者は大文字と小文字を混在させるためです(iam:PassRole、iam:passrole)。* ワイルドカードは小文字に変換されません。
例 - リソース ARN の照合
リソースが特定のロールを対象にできるステートメントを検索するには、リソース値を事前に集約し、リストに対して対象を一度だけ検査します(「直積の回避」を参照)。対象セットと HAS_RESOURCE の各項目を別個の MATCH で組み合わせないでください。直積が生成されます。
リソースのケースは、次の 3 つの述語で網羅できます。完全なワイルドカード(*)、対象名を含むパターン(arn:aws:iam::*:role/admin*)、および実際の ARN のプレフィックスまたは構成要素となるパターンです。
リストプロパティのカタログ
プロバイダーカタログは api/src/backend/tasks/jobs/attack_paths/provider_config.py(AWS_NORMALIZED_LISTS)にあります。ポリシーステートメントのほか、KMS アルゴリズム、ECS コンテナ定義のリスト(entry_point、command、links、dns_servers など)、CloudFront エイリアス、Inspector の検出 URL と脆弱性のリスト、RDS イベントサブスクリプションのカテゴリなどが含まれます。カタログにないリストプロパティをクエリする場合は、同期レイヤーで実体化されるよう、まずカタログにエントリを追加してください。
よく使う openCypher パターン
アカウント/プリンシパルの照合、サービス信頼の形状、およびロール引き受け先の形状は、上記のテンプレートとサブパターンに示されています。再利用できる追加のパターンは次のとおりです。
JSON エンコードされたプロパティ
オブジェクト型の Cartography プロパティ(特に AWSPolicyStatement と S3PolicyStatement の condition)は、JSON エンコードされた文字列として保存されます。例:'{"StringEquals":{"aws:SourceAccount":"123456789012"}}'。クエリ時に JSON を解析する機能はないため、部分文字列の確認には CONTAINS を使います。
WHERE stmt.condition CONTAINS '"aws:SourceAccount"'
構造化された内容を調べるには、行を取得して Python で解析します。Cypher では JSON オブジェクトのキーをたどれません。
パスの接続性を使った Internet ノード
OPTIONAL MATCH (internet:Internet)-[can_access:CAN_ACCESS]->(resource)
resource は、上記のアカウントを起点とするパターンによって、あらかじめバインドされている必要があります。
複数ラベルの OR(複数のリソースタイプ)
MATCH path = (aws:AWSAccount {id: $provider_uid})-[r]-(x)-[q]-(y)
WHERE (x:EC2PrivateIp AND x.public_ip = $ip)
OR (x:EC2Instance AND x.publicipaddress = $ip)
OR (x:NetworkInterface AND x.public_ip = $ip)
OR (x:ElasticIPAddress AND x.public_ip = $ip)
Prowler の検出結果を含める
同じノードのパス上の出現ごとに OPTIONAL MATCH が実行されるのを避けるため、型指定された検出結果のプローブの前にノードを重複排除します。
WITH collect(path_principal) + collect(path_target) AS paths
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr
ネットワーク露出に関するクエリでは、パスとともに Internet ノードとそのエッジを集約します。
WITH collect(path) AS paths, head(collect(internet)) AS internet, collect(can_access) AS can_access
UNWIND paths AS p
UNWIND nodes(p) AS n
WITH paths, internet, can_access, collect(DISTINCT n) AS unique_nodes
UNWIND unique_nodes AS n
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})
RETURN paths, collect(DISTINCT pf) as dpf, collect(DISTINCT pfr) as dpfr,
internet, can_access
Prowler 固有のラベルとリレーションシップ
これらは同期タスクによって追加され、Cartography スキーマには含まれません。それ以外については、固定された Cartography スキーマを参照してください(「作成手順」を参照)。
| ラベル/リレーションシップ | 説明 |
|---|---|
ProwlerFinding | 検出結果ノード(status、severity、check_id) |
Internet | Internet を示すセンチネルノード |
CAN_ACCESS | (Internet)-[:CAN_ACCESS]->(resource) 露出エッジ |
HAS_FINDING | (resource)-[:HAS_FINDING]->(:ProwlerFinding) 検出結果リンク |
TRUSTS_AWS_PRINCIPAL | ロールの信頼リレーションシップ |
STS_ASSUMEROLE_ALLOW | ロールを引き受け可能 |
パラメーター
ユーザー入力を受け取るクエリの場合:
parameters=[
AttackPathsQueryParameterDefinition(
name="ip",
label="IP address",
# data_type defaults to "string", cast defaults to str.
# For non-string params, set both: data_type="integer", cast=int
description="Public IP address, e.g. 192.0.2.0.",
placeholder="192.0.2.0",
),
],
openCypher との互換性
クエリは Neo4j と Amazon Neptune の両方で実行できる必要があります。次の構文は避けてください。
| 機能 | 代わりに使用するもの |
|---|---|
APOC プロシージャ(apoc.*) | グラフ内の実際のノードとリレーションシップ |
| Neptune 拡張機能 | 標準の openCypher |
reduce() | UNWIND + collect() |
FOREACH | WITH + UNWIND + SET |
正規表現 =~ | toLower() + 完全一致、または STARTS WITH / CONTAINS |
CALL () { UNION } | WHERE 内の複数ラベルに対する OR(上記のパターンを参照) |
| 引き継いだ値と集約式 | まず集約を射影します。例:WITH principal_paths, collect(...) AS target_paths。次に、後続の WITH でリストを結合します |
any(x IN list ...) | size([x IN list WHERE pred]) > 0 |
all(x IN list ...) | size([x IN list WHERE pred]) = size(list) |
none(x IN list ...) | size([x IN list WHERE pred]) = 0 |
EXISTS { MATCH (pattern) WHERE pred } | 単独の MATCH (pattern) + WHERE pred を使用します。下流の collect(path...) の前に WITH DISTINCT <path-vars> を置き、結合を重複排除します |
カタログ内のリスト型プロパティ(action、resource など)については、「リスト型プロパティを子ノードとして扱う」に示す複数の MATCH を使った形式で、HAS_* エッジをたどって子項目ノードにアクセスしてください。親ノードはリストを単一のフィールドとして保持しないため、split(...) やカンマ区切り文字列を使った述語は適用できません。
ベストプラクティス
- すべての MATCH をアカウントアンカーから連結します。 アンカーのない
MATCH (role:AWSRole)は、グラフ内のすべてのプロバイダーのロールを返しますが、MATCH (aws)--(role:AWSRole)はスコープが限定されます。2つ目の権限の MATCH、たとえばMATCH (principal)--(policy2:AWSPolicy)--(stmt2:AWSPolicyStatement)は、principalがすでにアカウントのサブグラフにバインドされているため安全です。 - 検出結果のプローブに型を指定します。 常に
OPTIONAL MATCH (n)-[pfr:HAS_FINDING]-(pf:{PROWLER_FINDING_LABEL} {{status: 'FAIL'}})を使用します。型を指定すると、Neptune はインラインのエッジフィルターを適用できます。型を指定しないプローブは、次数の大きいノードのすべての隣接エッジをスキャンします。 - 各 MATCH にコメントを付けます。 各句に、その役割を説明するインラインの
// ...を1行ずつ記述します。 - 内部ラベルは使用しないでください。
_ProviderResource、_AWSResource、_Tenant_*、_Provider_*はシステムの分離ラベルであり、クエリテキスト(定義済みまたはカスタム)に含めてはいけません。 - 経路の接続性を介して Internet ノードに到達します。 単独の MATCH として使わず、
(internet:Internet)-[:CAN_ACCESS]->(resource)を使用します。 RETURNの契約を維持します。 標準の形ではpaths, dpf, dpfrを使用し、ネットワーク露出クエリではinternet, can_accessを追加します。シリアライザーとビジュアライザーはこれらの名前に依存しています。
命名規則
- ID:ケバブケースの
{provider}-{category}-{description}。例:aws-ec2-privesc-passrole-iam。 - 定数:SHOUTING*SNAKE_CASE の
{PROVIDER}*{CATEGORY}\_{DESCRIPTION}。例:AWS_EC2_PRIVESC_PASSROLE_IAM。
作成手順
-
まず queries モジュールを読み、既存のスタイルに合わせます。
api/src/backend/api/attack_paths/queries/ ├── __init__.py ├── types.py # dataclass definitions ├── registry.py └── {provider}.py -
固定バージョンの Cartography スキーマを取得します。 ラベル、プロパティ、リレーションシップを推測しないでください。依存関係の固定バージョンを確認します。
grep cartography api/pyproject.toml次に、その正確なタグのスキーマを取得します。
# Git pin (prowler-cloud/cartography@<TAG>): https://raw.githubusercontent.com/prowler-cloud/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md # PyPI pin (cartography==<TAG>): https://raw.githubusercontent.com/cartography-cncf/cartography/refs/tags/<TAG>/docs/root/modules/{provider}/schema.md -
クエリを作成します。 標準の定義済みテンプレートに、適切なサブパターン(権限昇格またはネットワーク露出)を組み合わせます。リスト型プロパティ(action/resource など)については、展開された子ノードを
[:HAS_ACTION]->(:AWSPolicyStatementActionItem)などでたどります(「子ノードとしてのリスト型プロパティ」とAWS_NORMALIZED_LISTSカタログを参照)。 -
登録します。 プロバイダーファイルの末尾にある
{PROVIDER}_QUERIESリストに定数を追加します。
参考資料
- pathfinding.cloud:https://github.com/DataDog/pathfinding.cloud(`curl | jq
を使用してください。集約されたpaths.json` は WebFetch で扱うには大きすぎます)。 - Cartography スキーマ(固定バージョンのタグ別):
https://raw.githubusercontent.com/{org}/cartography/refs/tags/{tag}/docs/root/modules/{provider}/schema.md。 - Neptune の openCypher 準拠状況:https://docs.aws.amazon.com/neptune/latest/userguide/feature-opencypher-compliance.html。
- openCypher 仕様:https://github.com/opencypher/openCypher。
- 同期コンバーター(
tasks/jobs/attack_paths/sync.py):tasks/jobs/attack_paths/provider_config.py::AWS_NORMALIZED_LISTSに記載されたリスト型ノードプロパティは、子アイテムノードとHAS_*エッジとして実体化されます。カタログに含まれないプロパティは、カンマ区切りの文字列にシリアライズされ、警告が1回だけ出力されます。どちらのシンクも同じ形式です。