

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# ガードクエリの定義とフィルタリング
<a name="query-and-filtering"></a>

このトピックでは、クエリの記述と、Guard ルール句の記述時のフィルタリングの使用について説明します。

## 前提条件
<a name="query-filtering-prerequisites"></a>

フィルタリングは高度な AWS CloudFormation Guard 概念です。フィルタリングについて学習する前に、以下の基本的なトピックを確認することをお勧めします。
+ [とは AWS CloudFormation Guard](what-is-guard.md)
+ [ルール、句の記述](writing-rules.md)

## クエリの定義
<a name="defining-queries"></a>

クエリ式は、階層データをトラバースするために書き込まれる単純なドット (`.`) で区切られた式です。クエリ式には、値のサブセットをターゲットとするフィルター式を含めることができます。クエリが評価されると、SQL クエリから返された結果セットと同様に、値のコレクションが生成されます。

次のクエリ例では、 CloudFormation テンプレートで`AWS::IAM::Role`リソースを検索します。

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

クエリは、次の基本原則に従います。
+ クエリの各ドット (`.`) 部分は、 `Resources`や `Properties.Encrypted.` などの明示的なキー用語が使用されると階層を通過します。クエリのいずれかの部分が受信データムと一致しない場合、Guard は取得エラーをスローします。
+ ワイルドカードを使用するクエリのドット (`.`) 部分は、そのレベルで構造体のすべての値を`*`横断します。
+ 配列ワイルドカードを使用するクエリのドット (`.`) 部分は、その配列のすべてのインデックス`[*]`を横断します。
+ すべてのコレクションは、角括弧 内でフィルターを指定することでフィルタリングできます`[]`。コレクションは、次の方法で検出できます。
  + データムで自然に発生する配列はコレクションです。 の例を次に示します。

    ポート: `[20, 21, 110, 190]`

    タグ: `[{"Key": "Stage", "Value": "PROD"}, {"Key": "App", "Value": "MyService"}]`
  + のような構造のすべての値をトラバースする場合 `Resources.*`
  + クエリ結果自体は、値をさらにフィルタリングできるコレクションです。次の例を参照してください。

    ```
    # Query all resources
    let all_resources = Resource.*
    
    # Filter IAM resources from query results
    let iam_resources = %resources[ Type == /IAM/ ]
    
    # Further refine to get managed policies
    let managed_policies = %iam_resources[ Type == /ManagedPolicy/ ]
    
    # Traverse each managed policy
    %managed_policies {
        # Do something with each policy
    }
    ```

CloudFormation テンプレートスニペットの例を次に示します。

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
  SampleInstance:
    Type: AWS::EC2::Instance
    ...
  SampleVPC:
     Type: AWS::EC2::VPC
    ...
  SampleSubnet1:
    Type: AWS::EC2::Subnet
    ...
  SampleSubnet2:
    Type: AWS::EC2::Subnet
    ...
```

このテンプレートに基づいて、トラバースされるパスは `SampleRole`で、選択した最終値は です`Type: AWS::IAM::Role`。

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
```

YAML `Resources.*[ Type == 'AWS::IAM::Role' ]` 形式のクエリの結果の値を次の例に示します。

```
- Type: AWS::IAM::Role
  ...
```

クエリを使用する方法には、次のようなものがあります。
+ 変数にクエリを割り当てて、それらの変数を参照してクエリ結果にアクセスできるようにします。
+ 選択した各値に対してテストするブロックを使用してクエリに従います。
+ クエリを基本句と直接比較します。

## 変数へのクエリの割り当て
<a name="queries-and-filtering-variables"></a>

Guard は、特定のスコープ内でワンショット変数の割り当てをサポートします。ガードルールの変数の詳細については、「」を参照してください[ガードルールでの変数の割り当てと参照](variables.md)。

変数にクエリを割り当てると、クエリを 1 回書き込み、Guard ルールの他の場所で参照できます。このセクションで後述するクエリ原則を示す変数割り当ての例を以下に示します。

```
#
# Simple query assignment
#
let resources = Resources.* # All resources

#
# A more complex query here (this will be explained below)
#
let iam_policies_allowing_log_creates = Resources.*[
    Type in [/IAM::Policy/, /IAM::ManagedPolicy/]
    some Properties.PolicyDocument.Statement[*] {
         some Action[*] == 'cloudwatch:CreateLogGroup'
         Effect == 'Allow'
    }
]
```

## クエリに割り当てられた変数の値を直接ループスルーする
<a name="variable-assigned-from-query"></a>

Guard は、クエリの結果に対する直接実行をサポートしています。次の例では、 `when`ブロックは CloudFormation テンプレートにある各`AWS::EC2::Volume`リソースの `Encrypted`、`VolumeType`、および `AvailabilityZone`プロパティに対してテストします。

```
let ec2_volumes = Resources.*[ Type == 'AWS::EC2::Volume' ] 

when %ec2_volumes !empty {
    %ec2_volumes {
        Properties {
            Encrypted == true
            VolumeType in ['gp2', 'gp3']
            AvailabilityZone in ['us-west-2b', 'us-west-2c']
        }
    }
}
```

## 句レベルの直接比較
<a name="direct-clause-level-comparisons"></a>

Guard は、直接比較の一部としてクエリもサポートしています。例えば、次のようになります。

```
let resources = Resources.*
    
    some %resources.Properties.Tags[*].Key == /PROD$/
    some %resources.Properties.Tags[*].Value == /^App/
```

前の例では、示されている形式で表される 2 つの句 ( `some`キーワードで始まる) は独立した句と見なされ、個別に評価されます。

### 単一句とブロック句の形式
<a name="single-versus-block-clause-form"></a>

まとめると、前のセクションで示した 2 つのサンプル句は、次のブロックと同等ではありません。

```
let resources = Resources.*

some %resources.Properties.Tags[*] {
    Key == /PROD$/
    Value == /^App/
}
```

このブロックは、コレクション内の各`Tag`値をクエリし、そのプロパティ値を予想されるプロパティ値と比較します。前のセクションの句の組み合わせ形式は、2 つの句を個別に評価します。次の入力を検討してください。

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

最初の形式の句は に評価されます`PASS`。最初の形式で最初の句を検証する場合、`Resources`、、`Properties`、および の次のパス`NotPRODEnd`は 値`Key`と一致し`Tags`、想定値 と一致しません`PROD`。

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

最初の形式の 2 番目の句でも同じことが起こります。`Resources`、`Properties`、、および のパスは`Tags`、値 `Value`と一致します`AppStart`。その結果、2 番目の句は独立しています。

全体的な結果は です`PASS`。

ただし、ブロックフォームは次のように評価されます。`Tags` 値ごとに、 `Key`と の両方`Value`が一致しているかどうかを比較します。次の例では、 `NotAppStart`と `NotPRODEnd`の値は一致しません。

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

評価では `Key == /PROD$/`と の両方がチェックされるため`Value == /^App/`、一致は完了しません。したがって、結果は です`FAIL`。

**注記**  
コレクションを使用する場合は、コレクション内の各要素の複数の値を比較するときに、 ブロック句フォームを使用することをお勧めします。コレクションがスカラー値のセットである場合、または単一の属性のみを比較する場合は、単一句フォームを使用します。

## クエリ結果と関連句
<a name="query-outcomes"></a>

すべてのクエリは値のリストを返します。キーの欠落、すべてのインデックスへのアクセス時の配列の空の値 (`Tags: []`)、空のマップ () に遭遇したときのマップの欠損値など、トラバーサルのどの部分でも、取得エラーが発生する`Resources: {}`可能性があります。

このようなクエリに対して句を評価する場合、すべての取得エラーは失敗と見なされます。唯一の例外は、クエリで明示的なフィルターを使用する場合です。フィルターを使用すると、関連する句はスキップされます。

次のブロック障害は、実行中のクエリに関連付けられています。
+ テンプレートにリソースが含まれていない場合、クエリは に評価され`FAIL`、関連するブロックレベルの句も に評価されます`FAIL`。
+ テンプレートに のような空のリソースブロックが含まれている場合`{ "Resources": {} }`、クエリは に評価され`FAIL`、関連するブロックレベルの句も に評価されます`FAIL`。
+ テンプレートにリソースが含まれているが、クエリに一致するものがない場合、クエリは空の結果を返し、ブロックレベルの句はスキップされます。

## クエリでのフィルターの使用
<a name="filtering"></a>

クエリのフィルターは、実質的に選択基準として使用されるガード句です。以下は、 句の構造です。

```
 <query> <operator> [query|value literal] [message] [or|OR]
```

フィルター[AWS CloudFormation Guard ルールの記述](writing-rules.md)を使用する際の以下の重要なポイントに注意してください。
+ [Conjunctive Normal Form (CNF) ](https://en.wikipedia.org/wiki/Conjunctive_normal_form)を使用して句を結合します。
+ 新しい行で各連結 (`and`) 句を指定します。
+ 2 つの句間の`or`キーワードを使用して、差分 (`or`) を指定します。

次の例は、結合句と非結合句を示しています。

```
resourceType == 'AWS::EC2::SecurityGroup'
InputParameters.TcpBlockedPorts not empty 

InputParameters.TcpBlockedPorts[*] {
    this in r(100, 400] or 
    this in r(4000, 65535]
}
```

### 選択基準に句を使用する
<a name="selection-criteria"></a>

任意のコレクションにフィルタリングを適用できます。フィルタリングは、 などのコレクションがすでに存在する入力内の属性に直接適用できます`securityGroups: [....]`。常に値のコレクションであるクエリにフィルタリングを適用することもできます。結合法線形式を含む句のすべての機能をフィルタリングに使用できます。

次の一般的なクエリは、CloudFormation テンプレートからタイプ別にリソースを選択するときによく使用されます。

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

クエリは、入力の `Resources`セクションに存在するすべての値`Resources.*`を返します。のテンプレート入力例の場合[クエリの定義](#defining-queries)、クエリは以下を返します。

```
- Type: AWS::IAM::Role
  ...
- Type: AWS::EC2::Instance
  ...
- Type: AWS::EC2::VPC
  ...
- Type: AWS::EC2::Subnet
  ...
- Type: AWS::EC2::Subnet
  ...
```

次に、このコレクションにフィルターを適用します。一致させる基準は です`Type == AWS::IAM::Role`。以下は、フィルターが適用された後のクエリの出力です。

```
- Type: AWS::IAM::Role
  ...
```

次に、`AWS::IAM::Role`リソースのさまざまな句を確認します。

```
let all_resources = Resources.*
let all_iam_roles = %all_resources[ Type == 'AWS::IAM::Role' ]
```

以下は、すべての `AWS::IAM::Policy`および `AWS::IAM::ManagedPolicy`リソースを選択するフィルタリングクエリの例です。

```
Resources.*[
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
]
```

次の例では、これらのポリシーリソースに `PolicyDocument`が指定されているかどうかを確認します。

```
Resources.*[ 
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
    Properties.PolicyDocument exists
]
```

### より複雑なフィルタリングニーズの構築
<a name="complex-filtering"></a>

イングレスおよびエグレスセキュリティグループ情報 AWS Config の設定項目の次の例を考えてみましょう。

```
---
resourceType: 'AWS::EC2::SecurityGroup'
configuration:
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      toPort: 172
      ipv4Ranges:
        - cidrIp: 10.0.0.0/24
        - cidrIp: 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
    - fromPort: 89
      ipProtocol: '-1'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
  ipPermissionsEgress:
    - ipProtocol: '-1'
      ipv6Ranges: []
      prefixListIds: []
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  tags:
    - key: Name
      value: good-sg-delete-me
  vpcId: vpc-0123abcd
InputParameter:
  TcpBlockedPorts:
    - 3389
    - 20
    - 21
    - 110
    - 143
```

次の点に注意してください。
+ `ipPermissions` (進入ルール) は、設定ブロック内のルールのコレクションです。
+ 各ルール構造には、CIDR ブロックのコレクションを指定する `ipv6Ranges` `ipv4Ranges`や などの属性が含まれています。

任意の IP アドレスからの接続を許可する進入ルールを選択し、ルールで TCP ブロックされたポートを公開できないことを確認するルールを記述しましょう。

次の例に示すように、IPv4 を対象とするクエリ部分から始めます。

```
configuration.ipPermissions[
    #
    # at least one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0'
]
```

`some` キーワードはこのコンテキストで便利です。すべてのクエリは、クエリに一致する値のコレクションを返します。デフォルトでは、Guard は、クエリの結果として返されたすべての値がチェックと照合されると評価します。ただし、この動作が常にチェックに必要な動作であるとは限りません。設定項目からの入力の次の部分を考慮してください。

```
ipv4Ranges: 
  - cidrIp: 10.0.0.0/24
  - cidrIp: 0.0.0.0/0 # any IP allowed
```

には 2 つの値があります`ipv4Ranges`。すべての`ipv4Ranges`値が で表される IP アドレスと等しいわけではありません`0.0.0.0/0`。少なくとも 1 つの値が と一致するかどうかを確認します`0.0.0.0/0`。クエリから返されたすべての結果が一致する必要はありませんが、少なくとも 1 つの結果が一致する必要があることを Guard に伝えます。`some` キーワードは、結果のクエリの 1 つ以上の値がチェックと一致するように Guard に指示します。一致するクエリ結果値がない場合、Guard はエラーをスローします。

次に、次の例に示すように IPv6 を追加します。

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
]
```

最後に、次の例では、プロトコルが ではないことを確認します`udp`。

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
    
    #
    # and ipProtocol is not udp
    #
    ipProtocol != 'udp' ] 
]
```

完全なルールは次のとおりです。

```
rule any_ip_ingress_checks
{

    let ports = InputParameter.TcpBlockedPorts[*]

    let targets = configuration.ipPermissions[
        #
        # if either ipv4 or ipv6 that allows access from any address
        #
        some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
        some ipv6Ranges[*].cidrIpv6 == '::/0'

        #
        # the ipProtocol is not UDP
        #
        ipProtocol != 'udp' ]
        
    when %targets !empty
    {
        %targets {
            ipProtocol != '-1'
            <<
              result: NON_COMPLIANT
              check_id: HUB_ID_2334
              message: Any IP Protocol is allowed
            >>

            when fromPort exists 
                 toPort exists 
            {
                let each_target = this
                %ports {
                    this < %each_target.fromPort or
                    this > %each_target.toPort
                    <<
                        result: NON_COMPLIANT
                        check_id: HUB_ID_2340
                        message: Blocked TCP port was allowed in range
                    >>
                }
            }

        }       
     }
}
```

### 含まれるタイプに基づいてコレクションを分離する
<a name="splitting-collection"></a>

Infrastructure as Code (IaC) 設定テンプレートを使用する場合、設定テンプレート内の他のエンティティへの参照を含むコレクションが表示されることがあります。以下は、 へのローカル参照、 への参照`TaskRoleArn`、`TaskArn`および直接文字列参照を使用して Amazon Elastic Container Service (Amazon ECS) タスクを記述する CloudFormation テンプレートの例です。

```
Parameters:
  TaskArn:
    Type: String
Resources:
  ecsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn: 'arn:aws:....'
      ExecutionRoleArn: 'arn:aws:...'
  ecsTask2:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        'Fn::GetAtt':
          - iamRole
          - Arn
      ExecutionRoleArn: 'arn:aws:...2'
  ecsTask3:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        Ref: TaskArn
      ExecutionRoleArn: 'arn:aws:...2'
  iamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:...3'
```

次のクエリについて考えます。

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]
```

このクエリは、サンプルテンプレートに表示される 3 つの`AWS::ECS::TaskDefinition`リソースすべてを含む値のコレクションを返します。次の例に示すように、`TaskRoleArn`ローカル参照`ecs_tasks`を含む を他の から分離します。

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]

let ecs_tasks_role_direct_strings = %ecs_tasks[ 
    Properties.TaskRoleArn is_string ]

let ecs_tasks_param_reference = %ecs_tasks[
    Properties.TaskRoleArn.'Ref' exists ]

rule task_role_from_parameter_or_string {
    %ecs_tasks_role_direct_strings !empty or
    %ecs_tasks_param_reference !empty
}

rule disallow_non_local_references {
    # Known issue for rule access: Custom message must start on the same line
    not task_role_from_parameter_or_string 
    <<
        result: NON_COMPLIANT
        message: Task roles are not local to stack definition
    >>
}
```