

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

# 撰寫 AWS CloudFormation Guard 規則
<a name="writing-rules"></a>

在 中 AWS CloudFormation Guard，*規則*是policy-as-code規則。您可以使用 Guard 網域特定語言 (DSL) 撰寫規則，以驗證 JSON 或 YAML 格式的資料。規則由 *子句*組成。

您可以將使用 Guard DSL 寫入的規則儲存到使用任何副檔名的純文字檔案中。

您可以建立多個規則檔案，並將其分類為*規則集*。規則集可讓您同時針對多個規則檔案驗證 JSON 或 YAML 格式的資料。

**Topics**
+ [子句](#clauses)
+ [在 子句中使用查詢](#clauses-queries)
+ [在 子句中使用運算子](#clauses-operators)
+ [在 子句中使用自訂訊息](#clauses-custom-messages)
+ [合併子句](#combining-clauses)
+ [搭配 Guard 規則使用區塊](#blocks)
+ [使用內建函數](#built-in-functions)
+ [定義 Guard 查詢和篩選](query-and-filtering.md)
+ [在 Guard 規則中指派和參考變數](variables.md)
+ [在 中編寫具名規則區塊 AWS CloudFormation Guard](named-rule-block-composition.md)
+ [撰寫 子句以執行內容感知評估](context-aware-evaluations.md)

## 子句
<a name="clauses"></a>

子句是評估為 true (`PASS`) 或 false () 的布林表達式`FAIL`。子句使用二進位運算子來比較在單一值上操作的兩個值或單一運算子。

**Unary 子句的範例**

下列 unary 子句會評估集合是否為`TcpBlockedPorts`空。

```
InputParameters.TcpBlockedPorts not empty
```

下列 unary 子句會評估 `ExecutionRoleArn` 屬性是否為字串。

```
Properties.ExecutionRoleArn is_string
```

**二進位子句的範例**

下列二進位子句會評估 `BucketName` 屬性是否包含字串 `encrypted`，無論大小寫為何。

```
Properties.BucketName != /(?i)encrypted/
```

下列二進位子句會評估 `ReadCapacityUnits` 屬性是否小於或等於 5，000。

```
Properties.ProvisionedThroughput.ReadCapacityUnits <= 5000
```

### 撰寫 Guard 規則子句的語法
<a name="clauses-syntax"></a>

```
<query> <operator> [query|value literal] [custom message]
```

### Guard 規則子句的屬性
<a name="clauses-properties"></a>

`query`  <a name="clauses-properties-query"></a>
以點 (`.`) 分隔的表達式，寫入以周遊階層資料。查詢表達式可以包含篩選條件表達式，以值的子集為目標。您可以將查詢指派給變數，以便您可以寫入它們一次，並在規則集中的其他位置參考它們，這可讓您存取查詢結果。  
如需撰寫查詢和篩選的詳細資訊，請參閱 [定義查詢和篩選](query-and-filtering.md)。  
 *必要*：是

`operator`  <a name="clauses-properties-operator"></a>
單一或二進位運算子，可協助檢查查詢的狀態。二進位運算子的左側 (LHS) 必須是查詢，而右側 (RHS) 必須是查詢或值常值。  
 *支援的二進位運算子*：`==`（等於） \$1 `!=` （不等於） \$1 `>`（大於） \$1 `>=`（大於或等於） \$1 `<`（小於） \$1 `<=`（小於或等於） \$1 `IN`（在形式 【x、y、z】 的清單中  
 *支援的 unary 運算子*： `exists` \$1 `empty` \$1 `is_string` \$1 `is_list` \$1 `is_struct` \$1 `not(!)`  
 *必要*：是

`query|value literal`  <a name="clauses-properties-value-literal"></a>
查詢或支援的值常值，例如 `string`或 `integer(64)`。  
*支援的值常值*：  
+ 所有基本類型：`string`、`integer(64)`、`float(64)`、`bool`、`char`、 `regex`
+ 表達 `integer(64)`、 `float(64)`或 範圍的所有專用`char`範圍類型，表示為：
  + `r[<lower_limit>, <upper_limit>]`，其會轉譯為滿足下列表達`k`式的任何值： `lower_limit <= k <= upper_limit`
  + `r[<lower_limit>, <upper_limit>`)`k`，其會轉譯為滿足下列表達式的任何值： `lower_limit <= k < upper_limit`
  + `r(<lower_limit>, <upper_limit>]`，其會轉譯為滿足下列表達`k`式的任何值： `lower_limit < k <= upper_limit`
  + `r(<lower_limit>, <upper_limit>),` 可轉換為滿足下列表達`k`式的任何值： `lower_limit < k < upper_limit`
+ 巢狀索引鍵/值結構資料的關聯陣列 （對應）。例如：

  `{ "my-map": { "nested-maps": [ { "key": 10, "value": 20 } ] } }`
+ 基本類型或關聯陣列類型的陣列
 *必要*：有條件；使用二進位運算子時為必要。

`custom message`  <a name="clauses-properties-custom-message"></a>
提供 子句相關資訊的字串。訊息會顯示在 `validate`和 `test`命令的詳細輸出中，有助於了解或偵錯階層資料上的規則評估。  
 *必要*：否

## 在 子句中使用查詢
<a name="clauses-queries"></a>

如需撰寫查詢的資訊，請參閱 [定義查詢和篩選](query-and-filtering.md)和 [在 Guard 規則中指派和參考變數](variables.md)。

## 在 子句中使用運算子
<a name="clauses-operators"></a>

以下是 CloudFormation 範本`Template-1`和 的範例`Template-2`。為了示範如何使用支援的運算子，本節中的範例查詢和子句會參考這些範例範本。

**Template-1**

```
Resources:
 S3Bucket:
   Type: AWS::S3::Bucket
   Properties:
     BucketName: MyServiceS3Bucket
     BucketEncryption:
       ServerSideEncryptionConfiguration:
         - ServerSideEncryptionByDefault:
             SSEAlgorithm: 'aws:kms'
             KMSMasterKeyID: 'arn:aws:kms:us-east-1:123456789:key/056ea50b-1013-3907-8617-c93e474e400'
     Tags:
       - Key: stage
         Value: prod
       - Key: service
         Value: myService
```

**Template-2**

```
Resources:
 NewVolume:
   Type: AWS::EC2::Volume
   Properties: 
     Size: 100
     VolumeType: io1
     Iops: 100
     AvailabilityZone:
       Fn::Select:
         - 0
         - Fn::GetAZs: us-east-1
     Tags:
       - Key: environment
         Value: test
   DeletionPolicy: Snapshot
```

### 使用 unary 運算子的子句範例
<a name="clauses-unary-operators"></a>
+ `empty` – 檢查集合是否為空。您也可以使用它來檢查查詢在階層資料中是否有值，因為查詢會導致集合。您無法使用它來檢查字串值查詢是否已定義空字串 (`""`)。如需詳細資訊，請參閱[定義查詢和篩選](query-and-filtering.md)。

  下列子句會檢查範本是否已定義一或多個資源。它評估為 ，`PASS`因為具有邏輯 ID 的資源`S3Bucket`是在 中定義`Template-1`。

  ```
  Resources !empty
  ```

  下列子句會檢查是否為`S3Bucket`資源定義一或多個標籤。它會評估 為 ，`PASS`因為 中`S3Bucket`有兩個為 `Tags` 屬性定義的標籤`Template-1`。

  ```
  Resources.S3Bucket.Properties.Tags !empty
  ```
+ `exists` – 檢查每個查詢的出現是否具有值，並可用於取代 `!= null`。

  下列子句會檢查是否已為 定義 `BucketEncryption` 屬性`S3Bucket`。它評估為 ，`PASS`因為 `BucketEncryption` 是在 `S3Bucket`中為 定義的`Template-1`。

  ```
  Resources.S3Bucket.Properties.BucketEncryption exists
  ```

**注意**  
`empty` 和 `not exists`檢查會在周遊輸入資料時評估 `true` 是否遺失屬性索引鍵。例如，如果未在 的範本中定義 `Properties`區段`S3Bucket`，則 子句會`Resources.S3Bucket.Properties.Tag empty`評估為 `true`。`exists` 和 `empty`檢查不會在錯誤訊息中顯示文件內的 JSON 指標路徑。這兩個子句通常都有無法維護此周遊資訊的擷取錯誤。
+ `is_string` – 檢查查詢的每個出現是否為 `string`類型。

  下列子句會檢查是否為 `S3Bucket` 資源的 `BucketName` 屬性指定字串值。它會評估 為 ，`PASS`因為字串值`"MyServiceS3Bucket"`是在 `BucketName`中為 指定的`Template-1`。

  ```
  Resources.S3Bucket.Properties.BucketName is_string
  ```
+ `is_list` – 檢查查詢的每個出現是否為 `list`類型。

  下列子句會檢查是否為 `S3Bucket` 資源的 `Tags` 屬性指定清單。它評估為 ，`PASS`因為在 `Tags`中為 指定了兩個鍵值對`Template-1`。

  ```
  Resources.S3Bucket.Properties.Tags is_list
  ```
+ `is_struct` – 檢查查詢是否每次出現都是結構化資料。

  下列子句會檢查是否為 `S3Bucket` 資源的 `BucketEncryption` 屬性指定結構化資料。它會評估 為 ，`PASS`因為 `BucketEncryption` 是使用 中的`ServerSideEncryptionConfiguration`屬性類型 *（物件）* 指定`Template-1`。

  ```
  Resources.S3Bucket.Properties.BucketEncryption is_struct
  ```

**注意**  
若要檢查反轉狀態，您可以使用 (` not !`) 運算子搭配 `is_string`、 `is_list`和 `is_struct`運算子 。

### 使用二進位運算子的子句範例
<a name="clauses-binary-operators"></a>

下列子句會檢查 中`S3Bucket`資源`BucketName`屬性指定的值是否`Template-1`包含字串 `encrypt`，無論大小寫為何。這會評估 ，`PASS`因為指定的儲存貯體名稱`"MyServiceS3Bucket"`不包含字串 `encrypt`。

```
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
```

下列子句會檢查 中`NewVolume`資源`Size`屬性指定的值是否`Template-2`在特定範圍內：50 <= `Size` <= 200。它會評估 為 ，`PASS`因為 `100` 是針對 所指定`Size`。

```
Resources.NewVolume.Properties.Size IN r[50,200]
```

下列子句會檢查 中`NewVolume`資源`VolumeType`屬性指定的值是否為 `Template-2` `io1`、 `io2`或 `gp3`。它會評估 為 ，`PASS`因為 `io1` 是針對 所指定`NewVolume`。

```
Resources.NewVolume.Properties.NewVolume.VolumeType IN [ 'io1','io2','gp3' ]
```

**注意**  
本節中的範例查詢示範使用具有邏輯 IDs `S3Bucket`和 的資源來使用運算子`NewVolume`。資源名稱通常是使用者定義的，並且可以在基礎設施中任意命名為程式碼 (IaC) 範本。若要撰寫一般規則並套用至範本中定義的所有`AWS::S3::Bucket`資源，最常使用的查詢形式是 `Resources.*[ Type == ‘AWS::S3::Bucket’ ]`。如需詳細資訊，請參閱 [定義查詢和篩選](query-and-filtering.md) 以取得用量的詳細資訊，並探索 `cloudformation-guard` GitHub 儲存庫中[的範例](https://github.com/aws-cloudformation/cloudformation-guard/tree/main/guard-examples)目錄。

## 在 子句中使用自訂訊息
<a name="clauses-custom-messages"></a>

在下列範例中， 的 子句`Template-2`包含自訂訊息。

```
Resources.NewVolume.Properties.Size IN r(50,200) 
<<
    EC2Volume size must be between 50 and 200, 
    not including 50 and 200
>>
Resources.NewVolume.Properties.VolumeType IN [ 'io1','io2','gp3' ] <<Allowed Volume Types are io1, io2, and gp3>>
```

## 合併子句
<a name="combining-clauses"></a>

在 Guard 中，在新行上寫入的每個子句會使用 結合 （布林`and`邏輯） 隱含地與下一個子句結合。請參閱以下範例。

```
# clause_A ^ clause_B ^ clause_C
clause_A
clause_B
clause_C
```

您也可以透過`or|OR`在第一個子句結尾指定 ，使用 解散來結合子句與下一個子句。

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

在 Guard 子句中，會先評估接合點，接著評估接合點。Guard 規則可以定義為將評估為 (`PASS`) 或 `true`() 的 子句 `false`( `or|OR``and|AND`的 ) 解譯的組合`FAIL`。這類似於 [Conjunctive 正常形式](https://en.wikipedia.org/wiki/Conjunctive_normal_form)。

下列範例示範 子句的評估順序。

```
# (clause_E v clause_F) ^ clause_G
clause_E OR clause_F
clause_G

# (clause_H v clause_I) ^ (clause_J v clause_K)
clause_H OR
clause_I
clause_J OR
clause_K

# (clause_L v clause_M v clause_N) ^ clause_O
clause_L OR
clause_M OR
clause_N 
clause_O
```

所有以範例為基礎的子句`Template-1`都可以使用 結合來合併。請參閱以下範例。

```
Resources.S3Bucket.Properties.BucketName is_string
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
Resources.S3Bucket.Properties.BucketEncryption exists
Resources.S3Bucket.Properties.BucketEncryption is_struct
Resources.S3Bucket.Properties.Tags is_list
Resources.S3Bucket.Properties.Tags !empty
```

## 搭配 Guard 規則使用區塊
<a name="blocks"></a>

區塊是從一組相關子句、條件或規則中移除詳細程度和重複性的組合。區塊有三種類型：
+ 查詢區塊
+ `when` 區塊
+ 具名規則區塊

### 查詢區塊
<a name="query-blocks"></a>

以下是以範例 為基礎的 子句`Template-1`。結合用於結合 子句。

```
Resources.S3Bucket.Properties.BucketName is_string
Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
Resources.S3Bucket.Properties.BucketEncryption exists
Resources.S3Bucket.Properties.BucketEncryption is_struct
Resources.S3Bucket.Properties.Tags is_list
Resources.S3Bucket.Properties.Tags !empty
```

每個子句中的查詢表達式部分都會重複。您可以使用查詢區塊，改善可編譯性，並從具有相同初始查詢路徑的一組相關子句中移除詳細程度和重複性。可以寫入同一組子句，如下列範例所示。

```
Resources.S3Bucket.Properties {
    BucketName is_string
    BucketName != /(?i)encrypt/
    BucketEncryption exists
    BucketEncryption is_struct
    Tags is_list
    Tags !empty
}
```

在查詢區塊中，區塊前面的查詢會設定區塊內子句的內容。

如需使用區塊的詳細資訊，請參閱 [編寫具名規則區塊](named-rule-block-composition.md)。

### `when` 區塊
<a name="when-blocks"></a>

您可以使用採用下列格式的區塊來有條件地評估`when`區塊。

```
  when <condition> {
       Guard_rule_1
       Guard_rule_2
       ...
   }
```

`when` 關鍵字會指定`when`區塊的開頭。 `condition` 是 Guard 規則。只有在條件的評估結果為 `true`() 時，才會評估 區塊`PASS`。

以下是以 為基礎的範例`when`區塊`Template-1`。

```
when Resources.S3Bucket.Properties.BucketName is_string {
     Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
 }
```

只有在為 指定的值`BucketName`是字串時，才會評估`when`區塊中的 子句。如果在範本的 `Parameters`區段中參考為 `BucketName` 指定的值，如下列範例所示，則不會評估`when`區塊中的 子句。

```
Parameters:
   S3BucketName:
     Type: String
 Resources:
   S3Bucket:
     Type: AWS::S3::Bucket
     Properties:
       BucketName: 
         Ref: S3BucketName
     ...
```

### 具名規則區塊
<a name="named-rule-blocks"></a>

您可以將名稱指派給一組規則 (*規則集*)，然後在其他規則中參考這些模組化驗證區塊，稱為*命名規則區塊*。命名規則區塊採用下列形式。

```
  rule <rule name> [when <condition>] {
    Guard_rule_1
    Guard_rule_2
    ...
    }
```

`rule` 關鍵字會指定 name-rule 區塊的開頭。

`rule name` 是人類可讀取的字串，可唯一識別具名規則區塊。這是其封裝的 Guard 規則集的標籤。在此使用中，*Guard 規則*一詞包含子句、查詢區塊、`when`區塊和命名規則區塊。規則名稱可用來參考其封裝的規則集評估結果，這使得命名規則區塊可重複使用。規則名稱也提供 `validate`和 `test`命令輸出中規則失敗的相關內容。規則名稱會與區塊的評估狀態 (`PASS`、 `FAIL`或 `SKIP`) 一起顯示在規則檔案的評估輸出中。請參閱以下範例。

```
# Sample output of an evaluation where check1, check2, and check3 are rule names.
template.json Status = **FAIL**
**SKIP rules**
check1 **SKIP**
**PASS rules**
check2 **PASS**
**FAILED rules**
check3 **FAIL**
```

您也可以在規則名稱後面指定`when`關鍵字後面接著條件，以有條件的方式評估具名規則區塊。

以下是本主題先前討論的範例`when`區塊。

```
rule checkBucketNameStringValue when Resources.S3Bucket.Properties.BucketName is_string {
    Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
}
```

使用具名規則區塊，上述也可以編寫如下。

```
rule checkBucketNameIsString {
    Resources.S3Bucket.Properties.BucketName is_string
}
rule checkBucketNameStringValue when checkBucketNameIsString {
    Resources.S3Bucket.Properties.BucketName != /(?i)encrypt/
}
```

您可以使用其他 Guard 規則重複使用並分組具名規則區塊。以下是幾個範例。

```
rule rule_name_A {
    Guard_rule_1 OR
    Guard_rule_2
    ...
}

rule rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

rule rule_name_C {
    rule_name_A OR rule_name_B
}

rule rule_name_D {
    rule_name_A
    rule_name_B
}

rule rule_name_E when rule_name_D {
    Guard_rule_5
    Guard_rule_6
    ...
}
```

## 使用內建函數
<a name="built-in-functions"></a>

AWS CloudFormation Guard 提供內建函數，您可以在規則中用來執行字串操作、JSON 剖析和資料類型轉換等操作。函式僅透過指派至變數來支援。

### 關鍵函數
<a name="key-functions"></a>

`json_parse(json_string)`  
從範本剖析內嵌 JSON 字串。剖析之後，您可以評估所產生物件的屬性。

`count(collection)`  
傳回查詢解析的項目數量。

`regex_replace(base_string, regex_to_extract, regex_replacement)`  
使用規則表達式取代字串的一部分。

如需可用函數的完整清單，包括字串操作、集合操作和資料類型轉換函數，請參閱 Guard GitHub 儲存庫中的[函數文件](https://github.com/aws-cloudformation/cloudformation-guard/blob/main/docs/FUNCTIONS.md)。

# 定義 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
    >>
}
```

# 在 Guard 規則中指派和參考變數
<a name="variables"></a>

您可以在 AWS CloudFormation Guard 規則檔案中指派變數，以存放要在 Guard 規則中參考的資訊。Guard 支援一次性變數指派。變數會延遲評估，這表示 Guard 只會在規則執行時評估變數。

**Topics**
+ [指派變數](#assigning-variables)
+ [參考變數](#referencing-variables)
+ [變數範圍](#variable-scope)
+ [Guard 規則檔案中的變數範例](#variables-examples)

## 指派變數
<a name="assigning-variables"></a>

使用 `let`關鍵字來初始化和指派變數。最佳實務是針對變數名稱使用蛇案例。變數可以存放查詢產生的靜態常值或動態屬性。在下列範例中， 變數會`ecs_task_definition_task_role_arn`存放靜態字串值 `arn:aws:iam:123456789012:role/my-role-name`。

```
let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name'
```

在下列範例中， 變數會`ecs_tasks`儲存查詢的結果，以搜尋範本中的所有`AWS::ECS::TaskDefinition` CloudFormation 資源。您可以在撰寫規則時參考 `ecs_tasks` 來存取這些資源的相關資訊。

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

## 參考變數
<a name="referencing-variables"></a>

使用 `%`字首來參考變數。

根據 中的`ecs_task_definition_task_role_arn`變數範例[指派變數](#assigning-variables)，您可以在 Guard 規則子句的 `query|value literal`區段`ecs_task_definition_task_role_arn`中參考 。使用該參考可確保為 CloudFormation 範本中任何`AWS::ECS::TaskDefinition`資源的 `TaskDefinitionArn` 屬性指定的值是靜態字串值 `arn:aws:iam:123456789012:role/my-role-name`。

```
Resources.*.Properties.TaskDefinitionArn == %ecs_task_definition_role_arn
```

根據 中的`ecs_tasks`變數範例[指派變數](#assigning-variables)，您可以在查詢`ecs_tasks`中參考 （例如 %ecs\$1tasks.Properties)。首先，Guard 會評估變數，`ecs_tasks`然後使用傳回的值來周遊階層。如果變數`ecs_tasks`解析為非字串值，則 Guard 會擲回錯誤。

**注意**  
目前，Guard 不支援在自訂錯誤訊息內參考變數。

## 變數範圍
<a name="variable-scope"></a>

範圍是指規則檔案中定義的變數可見性。變數名稱只能在範圍內使用一次。有三個層級可以宣告變數，或三個可能的變數範圍：
+ **檔案層級** – 通常在規則檔案頂端宣告，您可以在規則檔案內的所有規則中使用檔案層級變數。它們對整個檔案可見。

  在下列範例規則檔案中，變數 `ecs_task_definition_task_role_arn`和 `ecs_task_definition_execution_role_arn` 會在檔案層級初始化。

  ```
  let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
  let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
  
  rule check_ecs_task_definition_task_role_arn
  {
      Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
  }
  ```
+ **規則層級** – 在規則中宣告，規則層級變數僅對該特定規則可見。規則以外的任何參考都會導致錯誤。

  在下列範例規則檔案中，變數 `ecs_task_definition_task_role_arn`和 `ecs_task_definition_execution_role_arn` 會在規則層級初始化。`ecs_task_definition_task_role_arn` 只能在`check_ecs_task_definition_task_role_arn`具名規則中參考 。您只能在`check_ecs_task_definition_execution_role_arn`具名規則中參考 `ecs_task_definition_execution_role_arn`變數。

  ```
  rule check_ecs_task_definition_task_role_arn
  {
      let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
      Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
      Resources.*.Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
  }
  ```
+ **區塊層級** – 在區塊內宣告，例如子`when`句，區塊層級變數只能由該特定區塊顯示。區塊以外的任何參考都會導致錯誤。

  在下列範例規則檔案中，變數 `ecs_task_definition_task_role_arn`和 `ecs_task_definition_execution_role_arn` 會在`AWS::ECS::TaskDefinition`類型區塊內的區塊層級初始化。您只能參考`AWS::ECS::TaskDefinition`類型區塊中的 `ecs_task_definition_task_role_arn`和 `ecs_task_definition_execution_role_arn`變數，以取得其個別規則。

  ```
  rule check_ecs_task_definition_task_role_arn
  {
      AWS::ECS::TaskDefinition
      {
          let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-task-role-name'
          Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
      }
  }
  
  rule check_ecs_task_definition_execution_role_arn
  {
      AWS::ECS::TaskDefinition
      {
          let ecs_task_definition_execution_role_arn = 'arn:aws:iam::123456789012:role/my-execution-role-name'
          Properties.ExecutionRoleArn == %ecs_task_definition_execution_role_arn
      }
  }
  ```

## Guard 規則檔案中的變數範例
<a name="variables-examples"></a>

下列各節提供變數靜態和動態指派的範例。

### 靜態指派
<a name="assigning-static-variables"></a>

以下是 CloudFormation 範本範例。

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
```

根據此範本，您可以撰寫稱為 的規則`check_ecs_task_definition_task_role_arn`，以確保所有`AWS::ECS::TaskDefinition`範本資源的 `TaskRoleArn` 屬性為 `arn:aws:iam::123456789012:role/my-role-name`。

```
rule check_ecs_task_definition_task_role_arn
{
    let ecs_task_definition_task_role_arn = 'arn:aws:iam::123456789012:role/my-role-name'
    Resources.*.Properties.TaskRoleArn == %ecs_task_definition_task_role_arn
}
```

在規則範圍內，您可以初始化名為 的變數`ecs_task_definition_task_role_arn`，並將靜態字串值 指派給該變數`'arn:aws:iam::123456789012:role/my-role-name'`。規則子句會參考 `query|value literal`區段中的 `ecs_task_definition_task_role_arn`變數，`arn:aws:iam::123456789012:role/my-role-name`檢查為 `EcsTask` 資源`TaskRoleArn`屬性指定的值是否為 。

### 動態指派
<a name="example-dynamic-assignment"></a>

以下是 CloudFormation 範本範例。

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 'arn:aws:iam::123456789012:role/my-role-name'
```

根據此範本，您可以初始化檔案`ecs_tasks`範圍內名為 的變數，並將查詢 指派給該變數`Resources.*[ Type == 'AWS::ECS::TaskDefinition'`。Guard 會查詢輸入範本中的所有資源，並將這些資源的相關資訊儲存在 中`ecs_tasks`。您也可以撰寫稱為 的規則`check_ecs_task_definition_task_role_arn`，以確保所有`AWS::ECS::TaskDefinition`範本資源的 `TaskRoleArn` 屬性為 `arn:aws:iam::123456789012:role/my-role-name`

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

rule check_ecs_task_definition_task_role_arn
{
    %ecs_tasks.Properties.TaskRoleArn == 'arn:aws:iam::123456789012:role/my-role-name'
}
```

規則子句會參考 `query`區段中的 `ecs_task_definition_task_role_arn`變數，`arn:aws:iam::123456789012:role/my-role-name`以檢查為 `EcsTask` 資源`TaskRoleArn`屬性指定的值是否為 。

### 強制執行 CloudFormation 範本組態
<a name="example-3"></a>

讓我們演練更複雜的生產使用案例範例。在此範例中，我們撰寫 Guard 規則，以確保更嚴格控制 Amazon ECS 任務的定義方式。

以下是 CloudFormation 範本範例。

```
Resources:
  EcsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      TaskRoleArn: 
        'Fn::GetAtt': [TaskIamRole, Arn]
      ExecutionRoleArn:
        'Fn::GetAtt': [ExecutionIamRole, Arn]

  TaskIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy'

  ExecutionIamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:iam::123456789012:policy/MyExamplePolicy'
```

根據此範本，我們會撰寫下列規則，以確保符合這些要求：
+ 範本中的每個`AWS::ECS::TaskDefinition`資源都連接了任務角色和執行角色。
+ 任務角色和執行角色是 AWS Identity and Access Management (IAM) 角色。
+ 角色在範本中定義。
+ 系統會為每個角色指定 `PermissionsBoundary` 屬性。

```
# Select all Amazon ECS task definition resources from the template
let ecs_tasks = Resources.*[
    Type == 'AWS::ECS::TaskDefinition'
]

# Select a subset of task definitions whose specified value for the TaskRoleArn property is an Fn::Gett-retrievable attribute
let task_role_refs = some %ecs_tasks.Properties.TaskRoleArn.'Fn::GetAtt'[0]

# Select a subset of TaskDefinitions whose specified value for the ExecutionRoleArn property is an Fn::Gett-retrievable attribute
let execution_role_refs = some %ecs_tasks.Properties.ExecutionRoleArn.'Fn::GetAtt'[0]

# Verify requirement #1
rule all_ecs_tasks_must_have_task_end_execution_roles 
    when %ecs_tasks !empty 
{
    %ecs_tasks.Properties {
        TaskRoleArn exists
        ExecutionRoleArn exists
    }
}

# Verify requirements #2 and #3
rule all_roles_are_local_and_type_IAM
    when all_ecs_tasks_must_have_task_end_execution_roles
{
    let task_iam_references = Resources.%task_role_refs
    let execution_iam_reference = Resources.%execution_role_refs

    when %task_iam_references !empty {
        %task_iam_references.Type == 'AWS::IAM::Role'
    }

    when %execution_iam_reference !empty {
        %execution_iam_reference.Type == 'AWS::IAM::Role'
    }
}

# Verify requirement #4
rule check_role_have_permissions_boundary
    when all_ecs_tasks_must_have_task_end_execution_roles
{
    let task_iam_references = Resources.%task_role_refs
    let execution_iam_reference = Resources.%execution_role_refs

    when %task_iam_references !empty {
        %task_iam_references.Properties.PermissionsBoundary exists
    }

    when %execution_iam_reference !empty {
        %execution_iam_reference.Properties.PermissionsBoundary exists
    }
}
```

# 在 中編寫具名規則區塊 AWS CloudFormation Guard
<a name="named-rule-block-composition"></a>

使用 撰寫具名規則區塊時 AWS CloudFormation Guard，您可以使用下列兩種合成樣式：
+ 條件相依性
+ 關聯性相依性

使用這些類型的相依性合成有助於提升可重複使用性，並減少具名規則區塊中的詳細程度和重複性。

**Topics**
+ [先決條件](#named-rules-prerequisites)
+ [條件相依性合成](#named-rules-conditional-dependency)
+ [關聯性相依性合成](#named-rules-correlational-dependency)

## 先決條件
<a name="named-rules-prerequisites"></a>

了解[撰寫規則](writing-rules.md#named-rule-blocks)中的具名規則區塊。

## 條件相依性合成
<a name="named-rules-conditional-dependency"></a>

在此合成風格中，`when`區塊或具名規則區塊的評估對一或多個其他具名規則區塊或子句的評估結果具有條件相依性。下列範例 Guard 規則檔案包含示範條件相依性的具名規則區塊。

```
# Named-rule block, rule_name_A
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# Example-1, Named-rule block, rule_name_B, takes a conditional dependency on rule_name_A
rule rule_name_B when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-2, when block takes a conditional dependency on rule_name_A
when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-3, Named-rule block, rule_name_C, takes a conditional dependency on rule_name_A ^ rule_name_B
rule rule_name_C when rule_name_A
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-4, Named-rule block, rule_name_D, takes a conditional dependency on (rule_name_A v clause_A) ^ clause_B ^ rule_name_B
rule rule_name_D when rule_name_A OR
                      clause_A
                      clause_B
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}
```

在上述範例規則檔案中， `Example-1`有下列可能的結果：
+ 如果 `rule_name_A`評估為 `PASS`，`rule_name_B`則會評估 封裝的 Guard 規則。
+ 如果 `rule_name_A`評估為 `FAIL`，`rule_name_B`則不會評估 封裝的 Guard 規則。 `rule_name_B`評估為 `SKIP`。
+ 如果 `rule_name_A`評估為 `SKIP`，`rule_name_B`則不會評估 封裝的 Guard 規則。 `rule_name_B`評估為 `SKIP`。
**注意**  
如果`rule_name_A`條件式取決於評估為 的規則，`FAIL`並導致`rule_name_A`評估為 的規則，就會發生這種情況`SKIP`。

以下是來自傳入和傳出安全群組資訊項目的組態管理資料庫 (CMDB) 組態 AWS Config 項目範例。此範例示範條件相依性合成。

```
rule check_resource_type_and_parameter {
    resourceType == /AWS::EC2::SecurityGroup/
    InputParameters.TcpBlockedPorts NOT EMPTY 
}

rule check_parameter_validity when check_resource_type_and_parameter {
    InputParameters.TcpBlockedPorts[*] {
        this in r[0,65535] 
    }
}

rule check_ip_procotol_and_port_range_validity when check_parameter_validity {
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let configuration = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"
        ipProtocol != 'udp' ] 
    when %configuration !empty {
        %configuration {
            ipProtocol != '-1'

            when fromPort exists 
                toPort exists {
                let ip_perm_block = this
                %ports {
                    this < %ip_perm_block.fromPort or
                    this > %ip_perm_block.toPort
                }
            }
        }
    }
}
```

在上述範例中， `check_parameter_validity` 有條件地相依於 `check_resource_type_and_parameter`，而 `check_ip_procotol_and_port_range_validity` 有條件地相依於 `check_parameter_validity`。以下是符合上述規則的組態管理資料庫 (CMDB) 組態項目。

```
---
version: '1.3'
resourceType: 'AWS::EC2::SecurityGroup'
resourceId: sg-12345678abcdefghi
configuration:
  description: Delete-me-after-testing
  groupName: good-sg-test-delete-me
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      ipv6Ranges: []
      prefixListIds: []
      toPort: 172
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      prefixListIds: []
      toPort: 89
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  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
InputParameters:
  TcpBlockedPorts:
    - 3389
    - 20
    - 110
    - 142
    - 1434
    - 5500
supplementaryConfiguration: {}
resourceTransitionStatus: None
```

## 關聯性相依性合成
<a name="named-rules-correlational-dependency"></a>

在此合成風格中，`when`區塊或具名規則區塊的評估對一或多個其他 Guard 規則的評估結果具有相互依存性。關聯性相依性可如下所示。

```
# Named-rule block, rule_name_A, takes a correlational dependency on all of the Guard rules encapsulated by the named-rule block
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# when block takes a correlational dependency on all of the Guard rules encapsulated by the when block
when condition {
    Guard_rule_1
    Guard_rule_2
    ...
}
```

為了協助您了解相互依存性合成，請檢閱 Guard 規則檔案的下列範例。

```
#
# Allowed valid protocols for AWS::ElasticLoadBalancingV2::Listener resources
#
let allowed_protocols = [ "HTTPS", "TLS" ]

let elbs = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::Listener' ]

#
# If there are AWS::ElasticLoadBalancingV2::Listener resources present, ensure that they have protocols specified from the 
# list of allowed protocols and that the Certificates property is not empty
#
rule ensure_all_elbs_are_secure when %elbs !empty {
    %elbs.Properties {
        Protocol in %allowed_protocols
        Certificates !empty
    }
}

# 
# In addition to secure settings, ensure that AWS::ElasticLoadBalancingV2::Listener resources are private
#
rule ensure_elbs_are_internal_and_secure when %elbs !empty {
    ensure_all_elbs_are_secure
    %elbs.Properties.Scheme == 'internal'
}
```

在上述規則檔案中， 對 `ensure_elbs_are_internal_and_secure`具有相互依存性`ensure_all_elbs_are_secure`。以下是符合上述規則的範例 CloudFormation 範本。

```
Resources:
  ServiceLBPublicListener46709EAA:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'
  ServiceLBPublicListener4670GGG:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'
```

# 撰寫 子句以執行內容感知評估
<a name="context-aware-evaluations"></a>

AWS CloudFormation Guard 子句會根據階層資料進行評估。Guard 評估引擎會使用簡單的虛線表示法，依照指定的階層式資料來解決對傳入資料的查詢。通常需要多個子句，才能根據資料映射或集合進行評估。Guard 提供方便的語法來寫入這類子句。引擎具有情境感知，並使用與評估相關聯的對應資料。

以下是包含容器的 Kubernetes Pod 組態範例，您可以將內容感知評估套用到其中。

```
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
    - name: app
      image: 'images.my-company.example/app:v4'
      resources:
        requests:
          memory: 64Mi
          cpu: 0.25
        limits:
          memory: 128Mi
          cpu: 0.5
    - name: log-aggregator
      image: 'images.my-company.example/log-aggregator:v6'
      resources:
        requests:
          memory: 64Mi
          cpu: 0.25
        limits:
          memory: 128Mi
          cpu: 0.75
```

您可以撰寫 Guard 子句來評估此資料。評估規則檔案時，內容是整個輸入文件。以下是驗證 Pod 中指定容器限制強制執行的範例子句。

```
#
# At this level, the root document is available for evaluation
#

#
# Our rule only evaluates for apiVersion == v1 and K8s kind is Pod
#
rule ensure_container_limits_are_enforced
    when apiVersion == 'v1'
        kind == 'Pod' 
{
    spec.containers[*] {
        resources.limits {
            #
            # Ensure that cpu attribute is set
            #
            cpu exists
            <<
                Id: K8S_REC_18
                Description: CPU limit must be set for the container
            >> 

            #
            # Ensure that memory attribute is set
            #
            memory exists
            <<
                Id: K8S_REC_22
                Description: Memory limit must be set for the container
            >>
        }
    }
}
```

## `context` 了解評估
<a name="context"></a>

在規則封鎖層級，傳入內容是完整的文件。`when` 條件的評估會針對 `apiVersion`和 `kind` 屬性所在的傳入根內容進行。在先前的範例中，這些條件會評估為 `true`。

現在，在 中周遊階層，`spec.containers[*]`如上述範例所示。對於階層的每個周遊，內容值會隨之變更。`spec` 區塊的周遊完成後，內容會變更，如下列範例所示。

```
containers:
  - name: app
    image: 'images.my-company.example/app:v4'
    resources:
      requests:
        memory: 64Mi
        cpu: 0.25
      limits:
        memory: 128Mi
        cpu: 0.5
  - name: log-aggregator
    image: 'images.my-company.example/log-aggregator:v6'
    resources:
      requests:
        memory: 64Mi
        cpu: 0.25
      limits:
        memory: 128Mi
        cpu: 0.75
```

周遊 `containers` 屬性之後，內容會顯示在下列範例中。

```
- name: app
  image: 'images.my-company.example/app:v4'
  resources:
    requests:
      memory: 64Mi
      cpu: 0.25
    limits:
      memory: 128Mi
      cpu: 0.5
- name: log-aggregator
  image: 'images.my-company.example/log-aggregator:v6'
  resources:
    requests:
      memory: 64Mi
      cpu: 0.25
    limits:
      memory: 128Mi
      cpu: 0.75
```

## 了解迴圈
<a name="loops"></a>

您可以使用 表達式`[*]`為 `containers` 屬性的陣列中包含的所有值定義迴圈。系統會針對 內的每個元素評估 區塊`containers`。在上述範例規則程式碼片段中，區塊中包含的子句會定義要根據容器定義驗證的檢查。內部包含的子句區塊會評估兩次，每個容器定義一次。

```
{
    spec.containers[*] {
       ...
    }
}
```

對於每次反覆運算，內容值是該對應索引的值。

**注意**  
支援的唯一索引存取格式為 `[<integer>]`或 `[*]`。目前，Guard 不支援 等範圍`[2..4]`。

## 陣列
<a name="arrays"></a>

通常在可接受陣列的地方，也會接受單一值。例如，如果只有一個容器，則可以捨棄陣列，並接受下列輸入。

```
apiVersion: v1
kind: Pod
metadata:
  name: frontend
spec:
  containers:
    name: app
    image: images.my-company.example/app:v4
    resources:
      requests:
        memory: "64Mi"
        cpu: 0.25
      limits:
        memory: "128Mi"
        cpu: 0.5
```

如果屬性可以接受陣列，請確定您的規則使用陣列形式。在上述範例中，您使用 `containers[*]`，而不是 `containers`。Guard 只會在遇到單一值表單時，在資料周遊時正確評估。

**注意**  
屬性接受陣列時，在表達規則子句的存取權時，請務必使用陣列形式。即使使用單一值，Guard 也會正確評估。

## 使用 表單`spec.containers[*]`而非 `spec.containers`
<a name="containers"></a>

Guard 查詢會傳回已解析值的集合。當您使用表單 時`spec.containers`，查詢的解析值包含 所參照的陣列`containers`，而非其中的元素。當您使用表單 時`spec.containers[*]`，您可以參考每個包含的個別元素。當您想要評估陣列中包含的每個元素時，請記得使用 `[*]`表單。

## 使用 `this` 參考目前的內容值
<a name="this"></a>

當您編寫 Guard 規則時，您可以使用 參考內容值`this`。通常， `this`是隱含的，因為它繫結到內容的值。例如，`this.apiVersion`、 `this.kind`和 `this.spec`繫結至根或文件。相反地， `this.resources` 繫結至 的每個值`containers`，例如 `/spec/containers/0/`和 `/spec/containers/1`。同樣地， `this.cpu` 和 `this.memory` 對應至限制，特別是 `/spec/containers/0/resources/limits`和 `/spec/containers/1/resources/limits`。

在下一個範例中，Kubernetes Pod 組態的上述規則會重寫為`this`明確使用。

```
rule ensure_container_limits_are_enforced
    when this.apiVersion == 'v1'
         this.kind == 'Pod' 
{
    this.spec.containers[*] {
        this.resources.limits {
            #
            # Ensure that cpu attribute is set
            #
            this.cpu exists
            <<
                Id: K8S_REC_18
                Description: CPU limit must be set for the container
            >> 

            #
            # Ensure that memory attribute is set
            #
            this.memory exists
            <<
                Id: K8S_REC_22
                Description: Memory limit must be set for the container
            >>
        }
    }
}
```

您不需要`this`明確使用 。不過，使用純量時`this`參考可能很有用，如下列範例所示。

```
InputParameters.TcpBlockedPorts[*] {
    this in r[0, 65535) 
    <<
        result: NON_COMPLIANT
        message: TcpBlockedPort not in range (0, 65535)
    >>
}
```

在上一個範例中， `this` 用於參考每個連接埠號碼。

## 使用隱含 的潛在錯誤 `this`
<a name="common-errors"></a>

編寫規則和子句時，參考隱含`this`內容值的元素時有一些常見的錯誤。例如，請考慮要評估的下列輸入基準 （這必須通過）。

```
resourceType: 'AWS::EC2::SecurityGroup'
InputParameters:
  TcpBlockedPorts: [21, 22, 110]
configuration:
  ipPermissions:
  - fromPort: 172
    ipProtocol: tcp
    ipv6Ranges: []
    prefixListIds: []
    toPort: 172
    userIdGroupPairs: []
    ipv4Ranges:
      - cidrIp: "0.0.0.0/0"   
  - fromPort: 89
    ipProtocol: tcp
    ipv6Ranges:
      - cidrIpv6: "::/0"
    prefixListIds: []
    toPort: 109
    userIdGroupPairs: []
    ipv4Ranges:
      - cidrIp: 10.2.0.0/24
```

根據上述範本進行測試時，下列規則會導致錯誤，因為它不正確地假設利用隱含 `this`。

```
rule check_ip_procotol_and_port_range_validity
{
    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            ipProtocol != '-1' # this here refers to each ipPermission instance
            InputParameters.TcpBlockedPorts[*] {
                fromPort > this or 
                toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }                
        }
    }
}
```

若要逐步解說此範例，請將上述規則檔案儲存為檔案名稱 `any_ip_ingress_check.guard`，並將資料儲存為檔案名稱 `ip_ingress.yaml`。然後，使用這些檔案執行下列`validate`命令。

```
cfn-guard validate -r any_ip_ingress_check.guard -d ip_ingress.yaml --show-clause-failures
```

在下列輸出中，引擎指出其嘗試擷取值 `InputParameters.TcpBlockedPorts[*]`上的屬性`/configuration/ipPermissions/0``/configuration/ipPermissions/1`失敗。

```
Clause #2     FAIL(Block[Location[file:any_ip_ingress_check.guard, line:17, column:13]])

              Attempting to retrieve array index or key from map at Path = /configuration/ipPermissions/0, Type was not an array/object map, Remaining Query = InputParameters.TcpBlockedPorts[*]

Clause #3     FAIL(Block[Location[file:any_ip_ingress_check.guard, line:17, column:13]])

              Attempting to retrieve array index or key from map at Path = /configuration/ipPermissions/1, Type was not an array/object map, Remaining Query = InputParameters.TcpBlockedPorts[*]
```

為了協助了解此結果，請使用`this`明確參考重新撰寫規則。

```
rule check_ip_procotol_and_port_range_validity
{
    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = this.configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            this.ipProtocol != '-1' # this here refers to each ipPermission instance
            this.InputParameters.TcpBlockedPorts[*] {
                this.fromPort > this or 
                this.toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }                
        }
    }
}
```

`this.InputParameters` 參考變數 內包含的每個值`any_ip_permissions`。指派給變數的查詢會選取相符`configuration.ipPermissions`的值。錯誤表示嘗試在此內容`InputParamaters`中擷取，但`InputParameters`位於根內容中。

內部區塊也會參考超出範圍的變數，如下列範例所示。

```
{
    this.ipProtocol != '-1' # this here refers to each ipPermission instance
    this.InputParameter.TcpBlockedPorts[*] { # ERROR referencing InputParameter off /configuration/ipPermissions[*]
        this.fromPort > this or # ERROR: implicit this refers to values inside /InputParameter/TcpBlockedPorts[*]
        this.toPort   < this 
        <<
            result: NON_COMPLIANT
            message: Blocked TCP port was allowed in range
        >>
    }
}
```

`this` 是指 中的每個連接埠值`[21, 22, 110]`，但也是指 `fromPort`和 `toPort`。它們都屬於外部區塊範圍。

### 解決隱含使用 的錯誤 `this`
<a name="common-errors-resolution"></a>

使用變數來明確指派和參考值。首先， `InputParameter.TcpBlockedPorts`是輸入 （根） 內容的一部分。移`InputParameter.TcpBlockedPorts`出內部區塊並明確指派，如下列範例所示。

```
rule check_ip_procotol_and_port_range_validity
{
     let ports = InputParameters.TcpBlockedPorts[*]
    # ... cut off for illustrating change
}
```

然後，明確參考此變數。

```
rule check_ip_procotol_and_port_range_validity
{
    #
    # Important: Assigning InputParameters.TcpBlockedPorts results in an ERROR. 
    # We need to extract each port inside the array. The difference is the query
    # InputParameters.TcpBlockedPorts returns [[21, 20, 110]] whereas the query 
    # InputParameters.TcpBlockedPorts[*] returns [21, 20, 110]. 
    #
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"

        ipProtocol != 'udp' ]
    
    when %any_ip_permissions !empty
    {
        %any_ip_permissions {
            this.ipProtocol != '-1' # this here refers to each ipPermission instance
            %ports {
                this.fromPort > this or 
                this.toPort   < this 
                <<
                    result: NON_COMPLIANT
                    message: Blocked TCP port was allowed in range
                >>
            }
        }
    }        
}
```

對 中的內部`this`參考執行相同的操作`%ports`。

不過，所有錯誤尚未修正，因為內部的迴圈`ports`仍有不正確的參考。下列範例顯示移除不正確的參考。

```
rule check_ip_procotol_and_port_range_validity
{
    #
    # Important: Assigning InputParameters.TcpBlockedPorts results in an ERROR. 
    # We need to extract each port inside the array. The difference is the query
    # InputParameters.TcpBlockedPorts returns [[21, 20, 110]] whereas the query 
    # InputParameters.TcpBlockedPorts[*] returns [21, 20, 110].
    #
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let any_ip_permissions = 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 %any_ip_permissions !empty
    {
        %any_ip_permissions {
            ipProtocol != '-1'
            <<
              result: NON_COMPLIANT
              check_id: HUB_ID_2334
              message: Any IP Protocol is allowed
            >>

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

接著，再次執行 `validate`命令。這次，它會通過。

```
cfn-guard validate -r any_ip_ingress_check.guard -d ip_ingress.yaml --show-clause-failures
```

以下是 `validate`命令的輸出。

```
ip_ingress.yaml Status = PASS
PASS rules
check_ip_procotol_and_port_range_validity    PASS
```

若要測試此方法是否失敗，下列範例會使用承載變更。

```
resourceType: 'AWS::EC2::SecurityGroup'
InputParameters:
  TcpBlockedPorts: [21, 22, 90, 110]
configuration:
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      ipv6Ranges: []
      prefixListIds: []
      toPort: 172
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: "0.0.0.0/0"   
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: "::/0"
      prefixListIds: []
      toPort: 109
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 10.2.0.0/24
```

90 的範圍介於 89–109 之間，且允許任何 IPv6 地址。以下是再次執行`validate`命令之後的輸出。

```
Clause #3           FAIL(Clause(Location[file:any_ip_ingress_check.guard, line:43, column:21], Check: _  LESS THAN %each_any_ip_perm.fromPort))
                    Comparing Int((Path("/InputParameters/TcpBlockedPorts/2"), 90)) with Int((Path("/configuration/ipPermissions/1/fromPort"), 89)) failed
                    (DEFAULT: NO_MESSAGE)
Clause #4           FAIL(Clause(Location[file:any_ip_ingress_check.guard, line:44, column:21], Check: _  GREATER THAN %each_any_ip_perm.toPort))
                    Comparing Int((Path("/InputParameters/TcpBlockedPorts/2"), 90)) with Int((Path("/configuration/ipPermissions/1/toPort"), 109)) failed

                                            result: NON_COMPLIANT
                                            check_id: HUB_ID_2340
                                            message: Blocked TCP port was allowed in range
```