

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 定義 Guard 查詢和篩選
<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
    ...
```

下列範例會顯示 `Resources.*[ Type == 'AWS::IAM::Role' ]` YAML 格式的查詢結果值。

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

您可以使用查詢的一些方式如下：
+ 將查詢指派給變數，以便透過參考這些變數來存取查詢結果。
+ 使用針對每個所選值進行測試的區塊來追蹤查詢。
+ 直接比較查詢與基本子句。

## 將查詢指派給變數
<a name="queries-and-filtering-variables"></a>

Guard 支援指定範圍內的一次性變數指派。如需 Guard 規則中變數的詳細資訊，請參閱 [在 Guard 規則中指派和參考變數](variables.md)。

您可以將查詢指派給變數，以便您寫入查詢一次，然後在 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`資源，針對 `VolumeType`、 `Encrypted`和 `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/
```

在上述範例中，以所示形式表示的兩個子句 （以`some`關鍵字開頭） 會被視為獨立子句，並分別進行評估。

### 單一子句和區塊子句表單
<a name="single-versus-block-clause-form"></a>

總而言之，上一節中顯示的兩個範例子句不等同於下列區塊。

```
let resources = Resources.*

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

此區塊會查詢集合中每個`Tag`值，並將其屬性值與預期的屬性值進行比較。上一節中子句的組合形式會獨立評估兩個子句。請考慮下列輸入。

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

第一個形式的子句會評估為 `PASS`。驗證第一個形式的第一個子句時，跨 `Resources`、`Tags`、 `Properties`和 的下列路徑`Key`符合 值`NotPRODEnd`，且不符合預期的 值`PROD`。

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

第一個表單的第二個子句也是如此。`Resources`、`Tags`、 `Properties`和 之間的路徑`Value`符合值 `AppStart`。因此，第二個子句會獨立。

整體結果為 `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>

查詢中的篩選條件實際上是做為選取條件的 Guard 子句。以下是 子句的結構。

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

當您使用篩選條件[撰寫 AWS CloudFormation Guard 規則](writing-rules.md)時，請記住下列重點：
+ 使用並[行法線格式 (CNF) ](https://en.wikipedia.org/wiki/Conjunctive_normal_form)結合子句。
+ 在新行上指定每個結合 (`and`) 子句。
+ 在兩個子句之間使用`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` （輸入規則） 是組態區塊內的規則集合。
+ 每個規則結構都包含屬性，例如 `ipv4Ranges`和 `ipv6Ranges`，以指定 CIDR 區塊的集合。

讓我們編寫規則，選取允許來自任何 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
```

有兩個值可供 使用`ipv4Ranges`。並非所有`ipv4Ranges`值都等於以 表示的 IP 地址`0.0.0.0/0`。您想要查看至少一個值是否符合 `0.0.0.0/0`。您告訴 Guard，不是從查詢傳回的所有結果都需要相符，但至少有一個結果必須相符。`some` 關鍵字會通知 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>

使用基礎設施做為程式碼 (IaC) 組態範本時，您可能會遇到一個集合，其中包含組態範本中其他實體的參考。以下是 CloudFormation 範本範例，描述 Amazon Elastic Container Service (Amazon ECS) 任務，其中包含 的本機參考`TaskArn`、 的`TaskRoleArn`參考，以及直接字串參考。

```
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' ]
```

此查詢會傳回值的集合，其中包含範例範本中顯示的所有三個`AWS::ECS::TaskDefinition`資源。分隔`ecs_tasks`包含來自其他 的`TaskRoleArn`本機參考，如下列範例所示。

```
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
    >>
}
```