

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

# 撰寫 子句以執行內容感知評估
<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
```