

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.

# Définition des requêtes Guard et filtrage
<a name="query-and-filtering"></a>

Cette rubrique traite de la rédaction de requêtes et de l'utilisation du filtrage lors de la rédaction de clauses de règles Guard.

## Conditions préalables
<a name="query-filtering-prerequisites"></a>

Le filtrage est un AWS CloudFormation Guard concept avancé. Nous vous recommandons de consulter les sujets fondamentaux suivants avant de vous familiariser avec le filtrage :
+ [Qu'est-ce que c'est AWS CloudFormation Guard ?](what-is-guard.md)
+ [Règles de rédaction, clauses](writing-rules.md)

## Définition des requêtes
<a name="defining-queries"></a>

Les expressions de requête sont de simples expressions séparées par des points (`.`) écrites pour parcourir des données hiérarchiques. Les expressions de requête peuvent inclure des expressions de filtre pour cibler un sous-ensemble de valeurs. Lorsque les requêtes sont évaluées, elles aboutissent à un ensemble de valeurs, similaire à un jeu de résultats renvoyé par une requête SQL.

L'exemple de requête suivant recherche des `AWS::IAM::Role` ressources dans un CloudFormation modèle.

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

Les requêtes suivent les principes de base suivants :
+ Chaque point (`.`) de la requête traverse la hiérarchie lorsqu'un terme clé explicite est utilisé, par exemple `Resources` ou `Properties.Encrypted.` si une partie de la requête ne correspond pas à la donnée entrante, Guard génère une erreur de récupération.
+ Un point (`.`) de la requête qui utilise un caractère générique `*` traverse toutes les valeurs de la structure à ce niveau.
+ Une partie point (`.`) de la requête qui utilise un caractère générique de tableau `[*]` parcourt tous les indices de ce tableau.
+ Toutes les collections peuvent être filtrées en spécifiant des filtres entre crochets`[]`. Les collections peuvent être rencontrées des manières suivantes :
  + Les tableaux présents naturellement dans le datum sont des collections. Voici quelques exemple de commandes  :

    Ports : `[20, 21, 110, 190]`

    Balises : `[{"Key": "Stage", "Value": "PROD"}, {"Key": "App", "Value": "MyService"}]`
  + Lorsque vous parcourez toutes les valeurs d'une structure telle que `Resources.*`
  + Tout résultat de requête est lui-même une collection à partir de laquelle les valeurs peuvent être filtrées davantage. Consultez l'exemple suivant.

    ```
    # Query all resources
    let all_resources = Resource.*
    
    # Filter IAM resources from query results
    let iam_resources = %resources[ Type == /IAM/ ]
    
    # Further refine to get managed policies
    let managed_policies = %iam_resources[ Type == /ManagedPolicy/ ]
    
    # Traverse each managed policy
    %managed_policies {
        # Do something with each policy
    }
    ```

Voici un exemple d'extrait CloudFormation de modèle.

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
  SampleInstance:
    Type: AWS::EC2::Instance
    ...
  SampleVPC:
     Type: AWS::EC2::VPC
    ...
  SampleSubnet1:
    Type: AWS::EC2::Subnet
    ...
  SampleSubnet2:
    Type: AWS::EC2::Subnet
    ...
```

Sur la base de ce modèle, le chemin parcouru est `SampleRole` et la valeur finale sélectionnée est`Type: AWS::IAM::Role`.

```
Resources:
  SampleRole:
    Type: AWS::IAM::Role
    ...
```

La valeur résultante de la requête `Resources.*[ Type == 'AWS::IAM::Role' ]` au format YAML est illustrée dans l'exemple suivant.

```
- Type: AWS::IAM::Role
  ...
```

Voici certaines des manières dont vous pouvez utiliser les requêtes :
+ Affectez une requête à des variables afin que les résultats de la requête soient accessibles en référençant ces variables.
+ Suivez la requête avec un bloc qui teste chacune des valeurs sélectionnées.
+ Comparez une requête directement à une clause de base.

## Affectation de requêtes à des variables
<a name="queries-and-filtering-variables"></a>

Guard prend en charge les assignations de variables ponctuelles dans un périmètre donné. Pour plus d'informations sur les variables dans les règles Guard, consultez[Affectation et référencement de variables dans les règles Guard](variables.md).

Vous pouvez attribuer des requêtes à des variables afin de pouvoir écrire des requêtes une seule fois, puis les référencer ailleurs dans vos règles Guard. Consultez les exemples d'attribution de variables suivants qui illustrent les principes de requête décrits plus loin dans cette section.

```
#
# Simple query assignment
#
let resources = Resources.* # All resources

#
# A more complex query here (this will be explained below)
#
let iam_policies_allowing_log_creates = Resources.*[
    Type in [/IAM::Policy/, /IAM::ManagedPolicy/]
    some Properties.PolicyDocument.Statement[*] {
         some Action[*] == 'cloudwatch:CreateLogGroup'
         Effect == 'Allow'
    }
]
```

## Parcours direct des valeurs d'une variable affectée à une requête
<a name="variable-assigned-from-query"></a>

Guard prend en charge l'exécution directe par rapport aux résultats d'une requête. Dans l'exemple suivant, le `when` bloc est testé par rapport à la `AvailabilityZone` propriété `Encrypted``VolumeType`, et pour chaque `AWS::EC2::Volume` ressource trouvée dans un CloudFormation modèle.

```
let ec2_volumes = Resources.*[ Type == 'AWS::EC2::Volume' ] 

when %ec2_volumes !empty {
    %ec2_volumes {
        Properties {
            Encrypted == true
            VolumeType in ['gp2', 'gp3']
            AvailabilityZone in ['us-west-2b', 'us-west-2c']
        }
    }
}
```

## Comparaisons directes au niveau des clauses
<a name="direct-clause-level-comparisons"></a>

Guard prend également en charge les requêtes dans le cadre de comparaisons directes. Par exemple, consultez ce qui suit.

```
let resources = Resources.*
    
    some %resources.Properties.Tags[*].Key == /PROD$/
    some %resources.Properties.Tags[*].Value == /^App/
```

Dans l'exemple précédent, les deux clauses (en commençant par le `some` mot clé) exprimées sous la forme illustrée sont considérées comme des clauses indépendantes et sont évaluées séparément.

### Formulaire de clause unique et de clause de bloc
<a name="single-versus-block-clause-form"></a>

Pris ensemble, les deux exemples de clauses présentés dans la section précédente ne sont pas équivalents au bloc suivant.

```
let resources = Resources.*

some %resources.Properties.Tags[*] {
    Key == /PROD$/
    Value == /^App/
}
```

Ce bloc interroge chaque `Tag` valeur de la collection et compare les valeurs de ses propriétés aux valeurs de propriété attendues. La forme combinée des clauses de la section précédente évalue les deux clauses indépendamment. Tenez compte de l'entrée suivante.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

Les clauses du premier formulaire sont évaluées à`PASS`. Lors de la validation de la première clause sous sa forme initiale, le chemin suivant traverse`Resources`, `Properties``Tags`, et `Key` correspond à la valeur `NotPRODEnd` et ne correspond pas à la valeur `PROD` attendue.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

Il en va de même pour la deuxième clause du premier formulaire. Le chemin traverse`Resources`, `Properties``Tags`, et `Value` correspond à la valeur`AppStart`. Par conséquent, la deuxième clause est indépendante.

Le résultat global est un`PASS`.

Cependant, le formulaire de bloc est évalué comme suit. Pour chaque `Tags` valeur, il compare si `Key` et `Value` si les valeurs correspondent ; `NotAppStart` et `NotPRODEnd` les valeurs ne correspondent pas dans l'exemple suivant.

```
Resources:
  ...
  MyResource:
    ...
    Properties:
      Tags:
        - Key: EndPROD
          Value: NotAppStart
        - Key: NotPRODEnd
          Value: AppStart
```

Parce que les évaluations vérifient les deux `Key == /PROD$/``Value == /^App/`, la correspondance n'est pas complète. Par conséquent, le résultat est`FAIL`.

**Note**  
Lorsque vous travaillez avec des collections, nous vous recommandons d'utiliser le formulaire de clause de blocage lorsque vous souhaitez comparer plusieurs valeurs pour chaque élément de la collection. Utilisez le formulaire à clause unique lorsque la collection est un ensemble de valeurs scalaires ou lorsque vous souhaitez uniquement comparer un seul attribut.

## Résultats de la requête et clauses associées
<a name="query-outcomes"></a>

Toutes les requêtes renvoient une liste de valeurs. Toute partie d'une traversée, telle qu'une clé manquante, des valeurs vides pour un tableau (`Tags: []`) lors de l'accès à tous les index, ou des valeurs manquantes pour une carte lorsque vous rencontrez une carte vide (`Resources: {}`), peut entraîner des erreurs de récupération.

Toutes les erreurs de récupération sont considérées comme des échecs lors de l'évaluation des clauses par rapport à de telles requêtes. La seule exception est lorsque des filtres explicites sont utilisés dans la requête. Lorsque des filtres sont utilisés, les clauses associées sont ignorées.

Les échecs de blocage suivants sont associés à l'exécution de requêtes.
+ Si un modèle ne contient pas de ressources, la requête est évaluée à`FAIL`, et les clauses de niveau de bloc associées sont également évaluées à`FAIL`.
+ Lorsqu'un modèle contient un bloc de ressources vide comme`{ "Resources": {} }`, la requête est évaluée à`FAIL`, et les clauses de niveau de bloc associées sont également évaluées à`FAIL`.
+ Si un modèle contient des ressources mais qu'aucune ne correspond à la requête, la requête renvoie des résultats vides et les clauses de niveau de bloc sont ignorées.

## Utilisation de filtres dans les requêtes
<a name="filtering"></a>

Les filtres dans les requêtes sont en fait des clauses Guard utilisées comme critères de sélection. Voici la structure d'une clause.

```
 <query> <operator> [query|value literal] [message] [or|OR]
```

Gardez à l'esprit les points essentiels suivants [AWS CloudFormation Guard Règles d'écriture](writing-rules.md) lorsque vous travaillez avec des filtres :
+ Combinez des clauses à l'aide de la [forme normale conjonctive (CNF](https://en.wikipedia.org/wiki/Conjunctive_normal_form)).
+ Spécifiez chaque clause de conjonction (`and`) sur une nouvelle ligne.
+ Spécifiez les disjonctions (`or`) en utilisant le `or` mot clé entre deux clauses.

L'exemple suivant illustre les clauses conjonctives et disjonctives.

```
resourceType == 'AWS::EC2::SecurityGroup'
InputParameters.TcpBlockedPorts not empty 

InputParameters.TcpBlockedPorts[*] {
    this in r(100, 400] or 
    this in r(4000, 65535]
}
```

### Utilisation de clauses pour les critères de sélection
<a name="selection-criteria"></a>

Vous pouvez appliquer un filtrage à n'importe quelle collection. Le filtrage peut être appliqué directement sur les attributs de l'entrée qui ressemblent déjà à une collection`securityGroups: [....]`. Vous pouvez également appliquer un filtrage à une requête, qui est toujours une collection de valeurs. Vous pouvez utiliser toutes les fonctionnalités des clauses, y compris la forme normale conjonctive, pour le filtrage.

La requête courante suivante est souvent utilisée lors de la sélection de ressources par type à partir d'un CloudFormation modèle.

```
Resources.*[ Type == 'AWS::IAM::Role' ]
```

La requête `Resources.*` renvoie toutes les valeurs présentes dans la `Resources` section de l'entrée. Pour l'exemple de modèle saisi dans[Définition des requêtes](#defining-queries), la requête renvoie ce qui suit.

```
- Type: AWS::IAM::Role
  ...
- Type: AWS::EC2::Instance
  ...
- Type: AWS::EC2::VPC
  ...
- Type: AWS::EC2::Subnet
  ...
- Type: AWS::EC2::Subnet
  ...
```

Maintenant, appliquez le filtre à cette collection. Le critère à respecter est`Type == AWS::IAM::Role`. Voici le résultat de la requête après l'application du filtre.

```
- Type: AWS::IAM::Role
  ...
```

Ensuite, vérifiez les différentes clauses relatives aux `AWS::IAM::Role` ressources.

```
let all_resources = Resources.*
let all_iam_roles = %all_resources[ Type == 'AWS::IAM::Role' ]
```

Voici un exemple de requête de filtrage qui sélectionne toutes les `AWS::IAM::ManagedPolicy` ressources `AWS::IAM::Policy` et toutes les ressources.

```
Resources.*[
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
]
```

L'exemple suivant vérifie si ces ressources de politique ont une valeur `PolicyDocument` spécifiée.

```
Resources.*[ 
    Type in [ /IAM::Policy/,
              /IAM::ManagedPolicy/ ]
    Properties.PolicyDocument exists
]
```

### Définition de besoins de filtrage plus complexes
<a name="complex-filtering"></a>

Prenons l'exemple suivant d'élément de AWS Config configuration pour les informations relatives aux groupes de sécurité d'entrée et de sortie.

```
---
resourceType: 'AWS::EC2::SecurityGroup'
configuration:
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      toPort: 172
      ipv4Ranges:
        - cidrIp: 10.0.0.0/24
        - cidrIp: 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
    - fromPort: 89
      ipProtocol: '-1'
      toPort: 189
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 1.1.1.1/32
  ipPermissionsEgress:
    - ipProtocol: '-1'
      ipv6Ranges: []
      prefixListIds: []
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  tags:
    - key: Name
      value: good-sg-delete-me
  vpcId: vpc-0123abcd
InputParameter:
  TcpBlockedPorts:
    - 3389
    - 20
    - 21
    - 110
    - 143
```

Notez ce qui suit :
+ `ipPermissions`(règles d'entrée) est un ensemble de règles à l'intérieur d'un bloc de configuration.
+ Chaque structure de règles contient des attributs tels que `ipv4Ranges` et `ipv6Ranges` pour spécifier une collection de blocs CIDR.

Écrivons une règle qui sélectionne toutes les règles d'entrée qui autorisent les connexions depuis n'importe quelle adresse IP et vérifie que les règles n'autorisent pas l'exposition des ports TCP bloqués.

Commencez par la partie de requête qui couvre IPv4, comme indiqué dans l'exemple suivant.

```
configuration.ipPermissions[
    #
    # at least one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0'
]
```

Le `some` mot clé est utile dans ce contexte. Toutes les requêtes renvoient un ensemble de valeurs correspondant à la requête. Par défaut, Guard évalue que toutes les valeurs renvoyées à la suite de la requête sont comparées aux vérifications. Toutefois, il se peut que ce comportement ne soit pas toujours celui dont vous avez besoin pour les vérifications. Tenez compte de la partie suivante de l'entrée provenant de l'élément de configuration.

```
ipv4Ranges: 
  - cidrIp: 10.0.0.0/24
  - cidrIp: 0.0.0.0/0 # any IP allowed
```

Deux valeurs sont présentes pour`ipv4Ranges`. Toutes les `ipv4Ranges` valeurs ne correspondent pas à une adresse IP désignée par`0.0.0.0/0`. Vous voulez voir si au moins une valeur correspond`0.0.0.0/0`. Vous indiquez à Guard qu'il n'est pas nécessaire que tous les résultats renvoyés par une requête correspondent, mais qu'au moins un résultat doit correspondre. Le `some` mot clé indique à Guard de s'assurer qu'une ou plusieurs valeurs de la requête résultante correspondent à la vérification. Si aucune valeur de résultat de requête ne correspond, Guard génère une erreur.

Ajoutez ensuite IPv6, comme indiqué dans l'exemple suivant.

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
]
```

Enfin, dans l'exemple suivant, confirmez que le protocole ne l'est pas`udp`.

```
configuration.ipPermissions[
    #
    # at-least-one ipv4Ranges equals ANY IPv4
    #
    some ipv4Ranges[*].cidrIp == '0.0.0.0/0' or
    #
    # at-least-one ipv6Ranges contains ANY IPv6
    #    
    some ipv6Ranges[*].cidrIpv6 == '::/0'
    
    #
    # and ipProtocol is not udp
    #
    ipProtocol != 'udp' ] 
]
```

Voici la règle complète.

```
rule any_ip_ingress_checks
{

    let ports = InputParameter.TcpBlockedPorts[*]

    let targets = 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 %targets !empty
    {
        %targets {
            ipProtocol != '-1'
            <<
              result: NON_COMPLIANT
              check_id: HUB_ID_2334
              message: Any IP Protocol is allowed
            >>

            when fromPort exists 
                 toPort exists 
            {
                let each_target = this
                %ports {
                    this < %each_target.fromPort or
                    this > %each_target.toPort
                    <<
                        result: NON_COMPLIANT
                        check_id: HUB_ID_2340
                        message: Blocked TCP port was allowed in range
                    >>
                }
            }

        }       
     }
}
```

### Séparer les collections en fonction de leurs types contenus
<a name="splitting-collection"></a>

Lorsque vous utilisez des modèles de configuration d'infrastructure en tant que code (IaC), vous pouvez rencontrer une collection contenant des références à d'autres entités dans le modèle de configuration. Voici un exemple de CloudFormation modèle qui décrit les tâches Amazon Elastic Container Service (Amazon ECS) avec une référence locale, une référence `TaskRoleArn` à et une référence `TaskArn` de chaîne directe.

```
Parameters:
  TaskArn:
    Type: String
Resources:
  ecsTask:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn: 'arn:aws:....'
      ExecutionRoleArn: 'arn:aws:...'
  ecsTask2:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        'Fn::GetAtt':
          - iamRole
          - Arn
      ExecutionRoleArn: 'arn:aws:...2'
  ecsTask3:
    Type: 'AWS::ECS::TaskDefinition'
    Metadata:
      SharedExectionRole: allowed
    Properties:
      TaskRoleArn:
        Ref: TaskArn
      ExecutionRoleArn: 'arn:aws:...2'
  iamRole:
    Type: 'AWS::IAM::Role'
    Properties:
      PermissionsBoundary: 'arn:aws:...3'
```

Considérons la requête suivante :

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]
```

Cette requête renvoie une collection de valeurs contenant les trois `AWS::ECS::TaskDefinition` ressources présentées dans l'exemple de modèle. Séparez `ecs_tasks` les références `TaskRoleArn` locales des autres, comme indiqué dans l'exemple suivant.

```
let ecs_tasks = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]

let ecs_tasks_role_direct_strings = %ecs_tasks[ 
    Properties.TaskRoleArn is_string ]

let ecs_tasks_param_reference = %ecs_tasks[
    Properties.TaskRoleArn.'Ref' exists ]

rule task_role_from_parameter_or_string {
    %ecs_tasks_role_direct_strings !empty or
    %ecs_tasks_param_reference !empty
}

rule disallow_non_local_references {
    # Known issue for rule access: Custom message must start on the same line
    not task_role_from_parameter_or_string 
    <<
        result: NON_COMPLIANT
        message: Task roles are not local to stack definition
    >>
}
```