

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 编写用于执行情境感知评估的子句
<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
```

如果属性可以接受数组，请确保您的规则使用数组形式。在前面的示例中，您使用的是 and `containers[*]` not `containers`。当Guard仅遇到单值形式时，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 在允许任何 IPv6 地址的 89—109 范围内。以下是再次运行该`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
```