

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

# Menulis klausa untuk melakukan evaluasi sadar konteks
<a name="context-aware-evaluations"></a>

AWS CloudFormation Guard klausa dievaluasi terhadap data hierarkis. Mesin evaluasi Guard menyelesaikan kueri terhadap data yang masuk dengan mengikuti data hierarkis seperti yang ditentukan, menggunakan notasi putus-putus sederhana. Seringkali, beberapa klausa diperlukan untuk mengevaluasi terhadap peta data atau koleksi. Guard menyediakan sintaks yang nyaman untuk menulis klausa tersebut. Mesin sadar kontekstual dan menggunakan data terkait yang terkait untuk evaluasi.

Berikut ini adalah contoh konfigurasi Kubernetes Pod dengan kontainer, di mana Anda dapat menerapkan evaluasi konteks-sadar.

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

Anda dapat membuat klausa Guard untuk mengevaluasi data ini. Saat mengevaluasi file aturan, konteksnya adalah seluruh dokumen input. Berikut ini adalah contoh klausul yang memvalidasi limit enforcement untuk container yang ditentukan dalam sebuah 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
            >>
        }
    }
}
```

## Pemahaman `context` dalam evaluasi
<a name="context"></a>

Pada tingkat blok aturan, konteks yang masuk adalah dokumen lengkap. Evaluasi untuk `when` kondisi terjadi terhadap konteks root yang masuk ini di mana `kind` atribut `apiVersion` and berada. Dalam contoh sebelumnya, kondisi ini mengevaluasi`true`.

Sekarang, lintasi hierarki yang `spec.containers[*]` ditunjukkan pada contoh sebelumnya. Untuk setiap lintasan hierarki, nilai konteks berubah sesuai dengan itu. Setelah traversal `spec` blok selesai, konteksnya berubah, seperti yang ditunjukkan pada contoh berikut.

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

Setelah melintasi `containers` atribut, konteksnya ditampilkan dalam contoh berikut.

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

## Memahami loop
<a name="loops"></a>

Anda dapat menggunakan ekspresi `[*]` untuk mendefinisikan loop untuk semua nilai yang terkandung dalam array untuk `containers` atribut. Blok dievaluasi untuk setiap elemen di dalamnya`containers`. Dalam cuplikan aturan contoh sebelumnya, klausa yang terkandung di dalam blok menentukan pemeriksaan yang akan divalidasi terhadap definisi kontainer. Blok klausa yang terkandung di dalamnya dievaluasi dua kali, satu kali untuk setiap definisi kontainer.

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

Untuk setiap iterasi, nilai konteks adalah nilai pada indeks yang sesuai.

**catatan**  
Satu-satunya format akses indeks yang didukung adalah `[<integer>]` atau`[*]`. Saat ini, Guard tidak mendukung rentang seperti`[2..4]`.

## Array
<a name="arrays"></a>

Seringkali di tempat-tempat di mana array diterima, nilai tunggal juga diterima. Misalnya, jika hanya ada satu kontainer, array dapat dijatuhkan dan input berikut diterima.

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

Jika atribut dapat menerima array, pastikan bahwa aturan Anda menggunakan formulir array. Dalam contoh sebelumnya, Anda menggunakan `containers[*]` dan tidak. `containers` Guard mengevaluasi dengan benar saat melintasi data ketika hanya menemukan bentuk nilai tunggal.

**catatan**  
Selalu gunakan formulir array saat mengekspresikan akses untuk klausa aturan ketika atribut menerima array. Guard mengevaluasi dengan benar bahkan dalam kasus bahwa satu nilai digunakan.

## Menggunakan formulir `spec.containers[*]` alih-alih `spec.containers`
<a name="containers"></a>

Kueri penjaga mengembalikan kumpulan nilai yang diselesaikan. Saat Anda menggunakan formulir`spec.containers`, nilai yang diselesaikan untuk kueri berisi array yang dirujuk oleh`containers`, bukan elemen di dalamnya. Ketika Anda menggunakan formulir`spec.containers[*]`, Anda merujuk ke setiap elemen individu yang terkandung. Ingatlah untuk menggunakan `[*]` formulir setiap kali Anda berniat untuk mengevaluasi setiap elemen yang terkandung dalam array.

## Menggunakan `this` untuk mereferensikan nilai konteks saat ini
<a name="this"></a>

Saat Anda membuat aturan Guard, Anda dapat mereferensikan nilai konteks dengan menggunakan`this`. Seringkali`this`, implisit karena terikat pada nilai konteks. Misalnya,`this.apiVersion`,`this.kind`, dan `this.spec` terikat pada root atau dokumen. Sebaliknya, `this.resources` terikat pada setiap nilai untuk`containers`, seperti `/spec/containers/0/` dan`/spec/containers/1`. Demikian pula, `this.cpu` dan `this.memory` memetakan ke batas, secara khusus `/spec/containers/0/resources/limits` dan`/spec/containers/1/resources/limits`. 

Dalam contoh berikutnya, aturan sebelumnya untuk konfigurasi Kubernetes Pod ditulis ulang untuk digunakan secara eksplisit. `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
            >>
        }
    }
}
```

Anda tidak perlu menggunakan secara `this` eksplisit. Namun, `this` referensi dapat berguna saat bekerja dengan skalar, seperti yang ditunjukkan pada contoh berikut.

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

Pada contoh sebelumnya, `this` digunakan untuk merujuk ke setiap nomor port.

## Potensi kesalahan dengan penggunaan implisit `this`
<a name="common-errors"></a>

Saat membuat aturan dan klausa, ada beberapa kesalahan umum saat mereferensikan elemen dari nilai konteks implisit. `this` Misalnya, pertimbangkan datum masukan berikut untuk dievaluasi (ini harus lulus).

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

Ketika diuji terhadap template sebelumnya, aturan berikut menghasilkan kesalahan karena membuat asumsi yang salah untuk memanfaatkan implisit. `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
                >>
            }                
        }
    }
}
```

Untuk menelusuri contoh ini, simpan file aturan sebelumnya dengan nama `any_ip_ingress_check.guard` dan data dengan nama file. `ip_ingress.yaml` Kemudian, jalankan `validate` perintah berikut dengan file-file ini.

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

Dalam output berikut, mesin menunjukkan bahwa usahanya untuk mengambil properti `InputParameters.TcpBlockedPorts[*]` pada nilai`/configuration/ipPermissions/0`, `/configuration/ipPermissions/1` gagal.

```
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[*]
```

Untuk membantu memahami hasil ini, tulis ulang aturan menggunakan referensi `this` eksplisit.

```
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`referensi setiap nilai yang terkandung di dalam variabel`any_ip_permissions`. Kueri yang ditetapkan ke variabel memilih `configuration.ipPermissions` nilai yang cocok. Kesalahan menunjukkan upaya untuk mengambil `InputParamaters` dalam konteks ini, tetapi `InputParameters` berada dalam konteks root.

Blok dalam juga mereferensikan variabel yang berada di luar cakupan, seperti yang ditunjukkan pada contoh berikut.

```
{
    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`mengacu pada setiap nilai port di`[21, 22, 110]`, tetapi juga mengacu pada `fromPort` dan`toPort`. Keduanya termasuk dalam lingkup blok luar.

### Menyelesaikan kesalahan dengan penggunaan implisit `this`
<a name="common-errors-resolution"></a>

Gunakan variabel untuk secara eksplisit menetapkan dan mereferensikan nilai. Pertama, `InputParameter.TcpBlockedPorts` adalah bagian dari konteks input (root). Pindah `InputParameter.TcpBlockedPorts` keluar dari blok dalam dan tetapkan secara eksplisit, seperti yang ditunjukkan pada contoh berikut.

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

Kemudian, lihat variabel ini secara eksplisit.

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

Lakukan hal yang sama untuk `this` referensi batin di dalam`%ports`.

Namun, semua kesalahan belum diperbaiki karena loop di dalamnya `ports` masih memiliki referensi yang salah. Contoh berikut menunjukkan penghapusan referensi yang salah.

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

Selanjutnya, jalankan `validate` perintah lagi. Kali ini, berlalu.

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

Berikut ini adalah output dari `validate` perintah.

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

Untuk menguji pendekatan ini untuk kegagalan, contoh berikut menggunakan perubahan payload.

```
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 berada dalam kisaran 89—109 yang memiliki IPv6 alamat apa pun yang diizinkan. Berikut ini adalah output dari `validate` perintah setelah menjalankannya lagi.

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