

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

# Rédaction de clauses pour effectuer des évaluations contextuelles
<a name="context-aware-evaluations"></a>

AWS CloudFormation Guard les clauses sont évaluées par rapport à des données hiérarchiques. Le moteur d'évaluation Guard résout les requêtes relatives aux données entrantes en suivant les données hiérarchiques telles que spécifiées, à l'aide d'une simple notation en pointillés. Plusieurs clauses sont souvent nécessaires pour effectuer une évaluation par rapport à une carte de données ou à une collection. Guard fournit une syntaxe pratique pour écrire de telles clauses. Le moteur est conscient du contexte et utilise les données correspondantes associées pour les évaluations.

Voici un exemple de configuration de Kubernetes Pod avec des conteneurs, à laquelle vous pouvez appliquer des évaluations contextuelles.

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

Vous pouvez créer des clauses Guard pour évaluer ces données. Lors de l'évaluation d'un fichier de règles, le contexte est l'intégralité du document d'entrée. Vous trouverez ci-dessous des exemples de clauses qui valident l'application des limites pour les conteneurs spécifiés dans un 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
            >>
        }
    }
}
```

## Compréhension `context` lors des évaluations
<a name="context"></a>

Au niveau du bloc de règles, le contexte entrant est le document complet. Les évaluations de la `when` condition sont effectuées par rapport à ce contexte racine entrant dans lequel se trouvent les `kind` attributs `apiVersion` et. Dans l'exemple précédent, ces conditions sont évaluées à`true`.

Maintenant, parcourez la hiérarchie `spec.containers[*]` comme indiqué dans l'exemple précédent. Pour chaque traversée de la hiérarchie, la valeur de contexte change en conséquence. Une fois la traversée du `spec` bloc terminée, le contexte change, comme indiqué dans l'exemple suivant.

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

Après avoir parcouru l'`containers`attribut, le contexte est illustré dans l'exemple suivant.

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

## Comprendre les boucles
<a name="loops"></a>

Vous pouvez utiliser l'expression `[*]` pour définir une boucle pour toutes les valeurs contenues dans le tableau de l'`containers`attribut. Le bloc est évalué pour chaque élément qu'il contient`containers`. Dans l'exemple d'extrait de règle précédent, les clauses contenues dans le bloc définissent les contrôles à valider par rapport à une définition de conteneur. Le bloc de clauses qu'il contient est évalué deux fois, une fois pour chaque définition de conteneur.

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

Pour chaque itération, la valeur de contexte est la valeur correspondant à l'index correspondant.

**Note**  
Le seul format d'accès à l'index pris en charge est `[<integer>]` ou`[*]`. Actuellement, Guard ne prend pas en charge les plages de ce type`[2..4]`.

## Arrays (tableaux)
<a name="arrays"></a>

Souvent, dans les endroits où un tableau est accepté, les valeurs uniques sont également acceptées. Par exemple, s'il n'y a qu'un seul conteneur, le tableau peut être supprimé et l'entrée suivante est acceptée.

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

Si un attribut peut accepter un tableau, assurez-vous que votre règle utilise la forme matricielle. Dans l'exemple précédent, vous utilisez `containers[*]` et non`containers`. Guard évalue correctement lorsqu'il parcourt les données lorsqu'elles ne rencontrent que le formulaire à valeur unique.

**Note**  
Utilisez toujours la forme de tableau lorsque vous exprimez l'accès à une clause de règle lorsqu'un attribut accepte un tableau. Guard évalue correctement même dans le cas où une seule valeur est utilisée.

## En utilisant le formulaire `spec.containers[*]` au lieu de `spec.containers`
<a name="containers"></a>

Les requêtes Guard renvoient un ensemble de valeurs résolues. Lorsque vous utilisez le formulaire`spec.containers`, les valeurs résolues pour la requête contiennent le tableau référencé par`containers`, et non les éléments qu'il contient. Lorsque vous utilisez le formulaire`spec.containers[*]`, vous faites référence à chaque élément individuel qu'il contient. N'oubliez pas d'utiliser le `[*]` formulaire chaque fois que vous avez l'intention d'évaluer chaque élément contenu dans le tableau.

## Utilisation `this` pour référencer la valeur de contexte actuelle
<a name="this"></a>

Lorsque vous créez une règle de garde, vous pouvez référencer la valeur de contexte en utilisant`this`. Souvent, elle `this` est implicite car elle est liée à la valeur du contexte. Par exemple, `this.apiVersion``this.kind`, et `this.spec` sont liés à la racine ou au document. En revanche, `this.resources` est lié à chaque valeur pour`containers`, telle que `/spec/containers/0/` et`/spec/containers/1`. De même, `this.cpu` et `this.memory` cartographiez les limites, en particulier `/spec/containers/0/resources/limits` et`/spec/containers/1/resources/limits`. 

Dans l'exemple suivant, la règle précédente pour la configuration de Kubernetes Pod est réécrite pour être utilisée explicitement. `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
            >>
        }
    }
}
```

Vous n'avez pas besoin d'utiliser `this` explicitement. Cependant, la `this` référence peut être utile lorsque vous travaillez avec des scalaires, comme le montre l'exemple suivant.

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

Dans l'exemple précédent, `this` est utilisé pour faire référence à chaque numéro de port.

## Erreurs potentielles liées à l'utilisation de l'implicite `this`
<a name="common-errors"></a>

Lors de la création de règles et de clauses, des erreurs fréquentes se produisent lors du référencement d'éléments à partir de la valeur de `this` contexte implicite. Par exemple, considérez la donnée d'entrée suivante à évaluer (elle doit être acceptée).

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

Lorsqu'elle est testée par rapport au modèle précédent, la règle suivante génère une erreur car elle suppose à tort qu'elle tire parti de l'implicite`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
                >>
            }                
        }
    }
}
```

Pour suivre cet exemple, enregistrez le fichier de règles précédent avec le nom `any_ip_ingress_check.guard` et les données avec le nom du fichier`ip_ingress.yaml`. Exécutez ensuite la `validate` commande suivante avec ces fichiers.

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

Dans le résultat suivant, le moteur indique que sa tentative de récupération d'une propriété `InputParameters.TcpBlockedPorts[*]` sur la valeur `/configuration/ipPermissions/0` a `/configuration/ipPermissions/1` échoué.

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

Pour mieux comprendre ce résultat, réécrivez la règle en utilisant des références `this` explicites.

```
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`fait référence à chaque valeur contenue dans la variable`any_ip_permissions`. La requête affectée à la variable sélectionne `configuration.ipPermissions` les valeurs correspondantes. L'erreur indique une tentative de récupération `InputParamaters` dans ce contexte, mais elle `InputParameters` s'est produite dans le contexte racine.

Le bloc interne fait également référence à des variables hors de portée, comme indiqué dans l'exemple suivant.

```
{
    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`fait référence à chaque valeur de port dans`[21, 22, 110]`, mais il fait également référence à `fromPort` et`toPort`. Ils appartiennent tous deux à la portée du bloc extérieur.

### Résoudre les erreurs à l'aide de l'utilisation implicite de `this`
<a name="common-errors-resolution"></a>

Utilisez des variables pour attribuer et référencer des valeurs de manière explicite. Tout d'abord, cela `InputParameter.TcpBlockedPorts` fait partie du contexte d'entrée (racine). `InputParameter.TcpBlockedPorts`Sortez du bloc interne et attribuez-le explicitement, comme indiqué dans l'exemple suivant.

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

Ensuite, faites référence à cette variable de manière explicite.

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

Procédez de même pour les `this` références internes à l'intérieur`%ports`.

Cependant, toutes les erreurs ne sont pas encore corrigées car la boucle à l'intérieur contient `ports` toujours une référence incorrecte. L'exemple suivant montre la suppression de la référence incorrecte.

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

Ensuite, exécutez à nouveau la `validate` commande. Cette fois, ça passe.

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

Le résultat de la `validate` commande est le suivant.

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

Pour tester cette approche en cas d'échec, l'exemple suivant utilise un changement de charge utile.

```
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 se situe dans la plage comprise entre 89 et 109 pour lesquelles n'importe quelle IPv6 adresse est autorisée. Voici le résultat de la `validate` commande après l'avoir exécutée à nouveau.

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