

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 컨텍스트 인식 평가를 수행하기 위한 절 작성
<a name="context-aware-evaluations"></a>

AWS CloudFormation Guard 절은 계층적 데이터에 대해 평가됩니다. Guard 평가 엔진은 간단한 점 표기법을 사용하여 지정된 계층적 데이터를 따라 수신 데이터에 대한 쿼리를 해결합니다. 데이터 맵 또는 컬렉션과 비교하여 평가하려면 여러 절이 필요한 경우가 많습니다. Guard는 이러한 절을 작성할 수 있는 편리한 구문을 제공합니다. 엔진은 상황에 맞게 인식되며 평가에 연결된 해당 데이터를 사용합니다.

다음은 컨텍스트 인식 평가를 적용할 수 있는 컨테이너가 있는 Kubernetes 포드 구성의 예입니다.

```
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 절을 작성하여이 데이터를 평가할 수 있습니다. 규칙 파일을 평가할 때 컨텍스트는 전체 입력 문서입니다. 다음은 포드에 지정된 컨테이너에 대한 제한 적용을 검증하는 예제 절입니다.

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

가드 쿼리는 해결된 값 모음을 반환합니다. 양식을 사용할 때 쿼리`spec.containers`의 확인된 값에는 안에 있는 요소가 `containers`아니라에서 참조하는 배열이 포함됩니다. 양식을 사용할 때 포함된 각 개별 요소를 `spec.containers[*]`참조합니다. 배열에 포함된 각 요소를 평가하려는 경우 항상 `[*]` 양식을 사용해야 합니다.

## `this`를 사용하여 현재 컨텍스트 값 참조
<a name="this"></a>

Guard 규칙을 작성할 때를 사용하여 컨텍스트 값을 참조할 수 있습니다`this`. `this`는 컨텍스트의 값에 바인딩되기 때문에 암시적인 경우가 많습니다. 예를 들어 , `this.kind`및 `this.apiVersion``this.spec`는 루트 또는 문서에 바인딩됩니다. 반대로 `this.resources`는 `/spec/containers/0/` 및 `containers`와 같은의 각 값에 바인딩됩니다`/spec/containers/1`. 마찬가지로 `this.cpu` 및는 제한, 특히 `/spec/containers/0/resources/limits` 및에 `this.memory` 매핑됩니다`/spec/containers/1/resources/limits`.

다음 예제에서는 Kubernetes 포드 구성에 대한 이전 규칙을 `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
```