Aller au contenu principal

Abonnements

Dans Red Kite, les abonnements servent à lancer des tâches (jobs) à partir d'une expression cron (abonnements cron) ou d'un finding (abonnements sur événement). Ils permettent d'étendre le flux d'automatisation de Red Kite.

Un abonnement peut appartenir à un projet. Dans ce cas, il ne s'applique qu'à ce projet. Si aucun projet n'est précisé pour un abonnement, il s'applique à tous les projets.

Certains abonnements sont intégrés à Red Kite. Ils sont identifiés comme tels, mais les utilisateurs peuvent tout de même les modifier. Il est même possible de supprimer un abonnement intégré, mais assurez-vous de savoir ce que vous faites.

Un abonnement, cron ou sur événement, ne se déclenche jamais sur une ressource bloquée.

Dans l'interface, un abonnement s'écrit au format yaml. Vous pouvez aussi choisir le projet auquel l'abonnement s'applique à l'aide du menu déroulant.

Un abonnement qui appartient à un projet est supprimé automatiquement lorsque ce projet est supprimé.

Abonnements cron​

Les abonnements cron sont lancés selon une expression cron. Ce sont les abonnements les plus simples : ils ne demandent que l'information nécessaire pour lancer une tâche.

Les abonnements cron peuvent être déclenchés de façon fiable jusqu'à toutes les vingt (20) secondes.

Même si les abonnements cron n'exigent pas de projet, au moins un projet doit exister au moment du déclenchement pour qu'au moins une tâche soit lancée.

Syntaxe d'un abonnement cron​

Un abonnement cron contient les éléments principaux suivants :

ÉlémentDescriptionRequis
nameLe nom de l'abonnement, pour s'y retrouver plus tardOui
cronExpressionL'expression cron qui précise quand lancer la tâcheOui
inputUne valeur qui représente toutes les ressources d'un typeNon
jobLa tâche à lancer lorsque l'expression cron se déclencheOui
cooldownLe délai minimal, en secondes, avant que l'abonnement puisse être redéclenché pour le même projet (par défaut : 0)Non
batchUn objet qui permet de regrouper les données pour lancer la tâche avec plusieurs ressourcesNon

Remarque : les sections suivantes donnent plus de détails sur ces éléments.

name​

  • Peut être n'importe quelle chaîne de caractères.
  • Sert à distinguer l'abonnement des autres.
  • Exemple :
name: my-first-cron-subscription

cronExpression​

  • Doit être une expression cron valide.
  • Précise l'horaire de déclenchement de la tâche.
  • Exemple (s'exécute tous les jours à minuit) :
cronExpression: "0 0 * * *"

input​

  • Élément facultatif qui définit le type de ressource à traiter.
  • Valeurs acceptées :
    • ALL_DOMAINS
    • ALL_HOSTS
    • ALL_IP_RANGES
    • ALL_TCP_PORTS
    • ALL_WEBSITES
  • Exemple :
input: ALL_WEBSITES

job​

  • L'élément job précise la tâche à exécuter et ses paramètres.

    Sous-élémentDescriptionRequis
    nameLe nom de la tâche (doit correspondre à un type existant).Oui
    parametersUne liste des paramètres requis par la tâche.Non
  • Exemple :

job:
name: resourceCheck
parameters:
- name: domain
value: example.com

cooldown​

  • Précise le délai minimal, en secondes, avant que la tâche puisse être redéclenchée pour le même projet.
  • Valeur par défaut : 0 (aucun délai).
  • Exemple (délai d'une heure) :
cooldown: 3600

batch​

  • L'élément batch permet de regrouper des ressources pour l'exécution d'une tâche.

    Sous-élémentDescriptionRequis
    enabledValeur booléenne qui active ou désactive le regroupement.Oui
    sizeTaille maximale des tableaux d'entrée dans chaque lot. Facultatif, sans limite par défaut.Non
  • Exemple (regroupement activé avec une taille de 50) :

batch:
enabled: true
size: 50

Exemple simple d'abonnement cron​

Voici l'un des abonnements les plus courts possible. Il se déclenche tous les jours à midi pour lancer une tâche DomainNameResolvingJob, avec example.com comme valeur du paramètre domainName.

Il résout donc le nom de domaine en adresse IP en lançant une tâche DomainNameResolvingJob. Si la résolution réussit, il émet un HostnameIpFinding.

Il recommence le lendemain à midi.

name: my cron subscription
cronExpression: 0 0 12 * * ?
job:
name: DomainNameResolvingJob
parameters:
- name: domainName
value: example.com

Délai de réactivation d'un abonnement cron​

Le délai de réactivation (cooldown) garantit qu'une tâche n'est lancée qu'aussi souvent que ce délai le permet. Par exemple, si une tâche est lancée à 10 h pour un projet et que l'abonnement cron a un délai de 3600 secondes (1 heure), le même abonnement ne pourra pas la relancer pour ce projet avant 11 h, même si l'expression cron se déclenche plus souvent.

Prenons aussi l'abonnement suivant. Son expression cron se déclenche toutes les heures, mais son délai est de 86400 secondes, soit 24 heures. Pour un projet donné, la tâche DomainNameResolvingJob n'est donc lancée qu'une fois par jour. L'abonnement vérifie toutefois toutes les heures s'il peut la lancer, à cause de l'expression cron.

name: my cron subscription
cronExpression: "0 */1 * * *"
input: ALL_DOMAINS
cooldown: 86400
job:
name: DomainNameResolvingJob
parameters:
- name: domainName
value: example.com

Variable input​

La variable input précise une source d'entrée. Le tableau suivant décrit les sources d'entrée possibles. La variable input est facultative. Les variables peuvent être injectées dans les paramètres de la tâche ainsi que dans les conditions.

Si un projet est précisé pour l'abonnement, seules les ressources de ce projet sont utilisées.

Source d'entréeVariablesVariables de regroupement
ALL_DOMAINS${domainName}${domainBatch}
ALL_HOSTS${ip}${ipBatch}
ALL_TCP_PORTS${ip}, ${port}, ${protocol}${ipBatch}, ${portBatch}, ${protocolBatch}
ALL_IP_RANGES${ip}, ${mask}${ipBatch}, ${maskBatch}
ALL_WEBSITES${domainName}, ${ip}, ${port}, ${protocol}, ${ssl}, ${path}${domainBatch}, ${ipBatch}, ${portBatch}, ${protocolBatch}, ${sslBatch}, ${pathBatch}

Avec l'entrée ALL_DOMAINS, vous avez accès à la variable injectable ${domainName}. Red Kite applique l'abonnement à tous les domaines et injecte la valeur de chaque domaine aux endroits indiqués.

name: Refreshing all domain names
input: ALL_DOMAINS
cronExpression: "0 0 * * *"
job:
name: DomainNameResolvingJob
parameters:
- name: domainName
value: ${domainName}

Avec l'entrée ALL_HOSTS, vous avez accès à la variable injectable ${ip}. Red Kite applique l'abonnement à tous les hôtes et injecte l'adresse IP de chaque hôte aux endroits indiqués.

name: Rescanning all hosts
input: ALL_HOSTS
cronExpression: "0 0 * * *"
job:
name: TcpPortScanningJob
parameters:
- name: targetIp
value: ${ip}
- name: threads
value: 1000
- name: socketTimeoutSeconds
value: 0.7
- name: portMin
value: 1
- name: portMax
value: 65535
- name: ports
value: []

Avec l'entrée ALL_TCP_PORTS, vous avez accès aux variables injectables ${ip}, ${port} et ${protocol}. Red Kite applique l'abonnement à tous les ports, et les valeurs de l'adresse IP, du port et du protocole peuvent être injectées aux endroits indiqués. Le protocole est soit tcp, soit udp.

Seuls les ports TCP sont transmis avec cette entrée. La variable protocol vaut donc toujours 'tcp'.

name: All tcp ports with condition
input: ALL_TCP_PORTS
cronExpression: "0 0 * * *"
job:
name: HttpServerCheckJob
parameters:
- name: targetIp
value: ${ip}
- name: ports
value: ["${port}"]
conditions:
- lhs: "${protocol}"
operator: "equals"
rhs: "tcp"

Avec l'entrée ALL_IP_RANGES, vous avez accès aux variables injectables ${ip} et ${mask}. Red Kite applique l'abonnement à toutes les plages d'adresses IP des projets.

name: Scanning IP ranges
cronExpression: 0 0 */7 * *
input: ALL_IP_RANGES
job:
name: TcpIpRangeScanningJob
parameters:
- name: targetIp
value: ${ip}
- name: targetMask
value: ${mask}
- name: rate
value: 100000
- name: portMin
value: 1
- name: portMax
value: 1000
- name: ports
value: []

Avec l'entrée ALL_WEBSITES, vous avez accès aux variables injectables ${domainName}, ${ip}, ${port}, ${path} et ${ssl}. Red Kite applique l'abonnement à tous les sites web et injecte les différentes valeurs de chaque site aux endroits indiqués.

name: Recrawling websites
cronExpression: 0 0 */7 * *
input: ALL_WEBSITES
job:
name: WebsiteCrawlingJob
parameters:
- name: domainName
value: ${domainName}
- name: targetIp
value: ${ip}
- name: port
value: ${port}
- name: path
value: ${path}
- name: ssl
value: ${ssl}
- name: maxDepth
value: 3
- name: crawlDurationSeconds
value: 1800
- name: fetcherConcurrency
value: 10
- name: inputParallelism
value: 10
- name: extraOptions
value: -jc -kf all -duc -j -or -ob -silent
Regrouper les entrées d'une tâche​

La syntaxe de regroupement (batching) d'un abonnement cron permet de lancer une tâche avec plusieurs valeurs en entrée. Au lieu de recevoir une simple chaîne de caractères, comme dans un abonnement cron standard, la tâche reçoit un tableau de chaînes, jusqu'à la taille indiquée.

Par exemple, l'abonnement suivant lance une tâche DomainNameResolvingJob en lui passant un tableau d'au plus 100 noms de domaine dans le paramètre domainNames, grâce à la variable d'entrée domainBatch.

Toutes les variables d'entrée possibles sont présentées dans ce tableau.

name: Refreshing all domain names
input: ALL_DOMAINS
batch:
enabled: true
size: 100
cronExpression: "0 0 */7 * *"
job:
name: DomainNameResolvingJob
parameters:
- name: domainNames
value: ${domainBatch}

Certaines ressources, comme les ports, fournissent plusieurs valeurs en entrée à un abonnement cron. Avec la syntaxe de regroupement, chaque valeur est transmise dans un tableau distinct, et toutes les valeurs situées à un même index i appartiennent à la même ressource. Tous les tableaux ont donc la même longueur.

Par exemple, supposons que la base de données contient trois ports avec les caractéristiques suivantes :

IndexPortIPProtocole
0221.1.1.1tcp
1802.2.2.2tcp
24433.3.3.3tcp

Et un abonnement cron comme celui-ci :

name: Example cron subscription
input: ALL_TCP_PORTS
batch:
enabled: true # Le regroupement des entrées est activé
size: 100 # avec un maximum de 100 éléments par tableau, par tâche
cronExpression: "0 0 */7 * *"
job:
name: PortExampleJob
parameters:
- name: targetIps
value: ${ipBatch}
- name: ports
value: ${portBatch}
- name: protocols
value: ${protocolBatch}

Après l'injection des valeurs par le backend, vous obtiendriez les paramètres suivants :

parameters:
- name: targetIps
value: ["1.1.1.1", "2.2.2.2", "3.3.3.3"]
- name: ports
value: [22, 80, 443]
- name: protocols
value: ["tcp", "tcp", "tcp"]

Vous pourriez ensuite les utiliser de la façon suivante, en Python :

import os
import json

ips = json.loads(os.environ.get("targetIps")) # ['1.1.1.1', '2.2.2.2', '3.3.3.3']
ports = json.loads(os.environ.get("ports")) # [22, 80, 443]
protocols = json.loads(os.environ.get("protocols")) # ['tcp', 'tcp', 'tcp']

Avec une taille de lot de 100 et seulement trois ports dans la base de données, une seule tâche a été lancée. Par contre, avec 10 ports dans la base de données et une taille de lot de 3, 4 tâches seraient lancées, selon la logique suivante :

ceiling(10 / 3) => 4

Abonnements sur événement​

Lorsque Red Kite trouve une information, que ce soit par la sortie d'une tâche ou par une saisie de l'utilisateur, un finding est émis. Ce finding contient l'information fournie par la tâche ou par l'utilisateur. À partir de cette information, de nouvelles tâches sont lancées et produisent à leur tour d'autres findings. C'est, en gros, le fonctionnement du flux d'automatisation de Red Kite.

Un abonnement sur événement permet de personnaliser ce flux d'automatisation en lançant n'importe quelle tâche à partir de n'importe quel finding. Ces tâches peuvent être lancées selon des conditions précises. De plus, la sortie du finding peut servir d'entrée à la tâche ainsi que de paramètre de condition.

Les abonnements sur événement ont aussi un délai de réactivation (cooldown), en secondes. Ce délai empêche qu'un abonnement soit déclenché trop souvent pour la même ressource. Il évite par exemple les boucles infinies ou une nouvelle analyse immédiate du même hôte.

Syntaxe d'un abonnement sur événement​

Un abonnement sur événement peut contenir les éléments principaux suivants :

ÉlémentDescriptionObligatoire
nameLe nom de l'abonnement, pour s'y retrouver plus tardOui
findingsLes findings auxquels réagir. Une liste de noms de findings.Oui
cooldownLe délai minimal, en secondes, avant que l'abonnement puisse être redéclenché pour la même ressourceOui
jobLa tâche à lancer lorsque le finding est reçu et que les conditions sont rempliesOui
conditionsLes conditions préalables au lancement de la tâcheNon

Remarque : les sections suivantes donnent plus de détails sur ces éléments.

  • L'élément name peut contenir n'importe quoi. Il sert uniquement à distinguer l'abonnement des autres.
  • Les éléments findings doivent être des types de findings existants. Consultez la documentation sur les findings pour en obtenir la liste.
  • L'élément cooldown est un nombre de secondes à attendre avant de relancer la tâche pour la même ressource.
  • L'élément job contient plusieurs valeurs :
    • name : obligatoire, doit être un type de tâche existant. Consultez la section sur les tâches pour la liste des valeurs valides.
    • parameters : facultatif, mais presque toujours nécessaire. Décrit, sous forme de liste, les valeurs d'entrée de la tâche par le name du paramètre et sa value.
  • L'élément conditions est le seul élément principal facultatif. S'il est absent, la tâche est toujours lancée lorsque le finding est trouvé. Vous pouvez fournir plusieurs conditions dans une liste. Une condition contient plusieurs éléments :
    • lhs : l'opérande de gauche.
    • operator : l'opérateur qui compare les deux opérandes.
    • rhs : l'opérande de droite.

Entrée dynamique d'un abonnement sur événement​

Vous pouvez ajouter une entrée dynamique à un abonnement sur événement en faisant référence aux champs d'un finding ou en injectant un secret.

Vous pouvez faire référence à une variable de sortie d'un finding par son nom, dans la valeur d'un paramètre de tâche ou dans l'opérande d'une condition, avec la syntaxe suivante : ${parameterName}. Le nom de la variable n'est pas sensible à la casse.

Un finding peut contenir des champs dynamiques dans le tableau fields. Les valeurs des champs dynamiques de type texte peuvent être injectées comme un champ ordinaire, avec la syntaxe ${parameterName}. Utilisez simplement la partie key du champ dynamique comme nom de variable, et son contenu data sera injecté.

Vous pouvez aussi injecter un secret comme valeur de paramètre avec la syntaxe ${secrets.secretName}. Apprenez-en plus sur les secrets ici.

Exemple simple d'abonnement sur événement​

Voici l'un des abonnements sur événement les plus courts possible. Il réagit à un HostnameFinding pour lancer une tâche DomainNameResolvingJob, en utilisant la sortie du finding comme valeur du paramètre domainName.

Il résout donc le nom de domaine trouvé dans le HostnameFinding en adresse IP en lançant une tâche DomainNameResolvingJob. Si la résolution réussit, il émet un HostnameIpFinding.

Le cooldown représente ici 23 heures. Par cet abonnement, un même nom d'hôte ne peut donc être résolu qu'une fois toutes les 23 heures au plus.

Cet abonnement est déjà intégré à Red Kite.

name: Domain name resolution
findings:
- HostnameFinding
cooldown: 82800
job:
name: DomainNameResolvingJob
parameters:
- name: domainName
value: ${domainName}

Exemple complexe d'abonnement sur événement​

Voici comment lancer une tâche TcpPortScanningJob lorsqu'un HostnameIpFinding est émis. La tâche ne démarre toutefois que si toutes les conditions sont remplies : l'ip trouvée par le finding doit contenir la chaîne « 13.37 » (sans égard à la casse), et 5 doit être supérieur ou égal à 3.

Si les conditions sont remplies lorsqu'un HostnameIpFinding est émis, la tâche démarre avec les paramètres indiqués. Remarquez que le paramètre targetIp contiendra la valeur de la variable de sortie ip du finding.

Dans ce cas, la tâche analyse les 1000 premiers ports de ${ip} ([1-1000]) ainsi que les ports 1234, 3389 et 8080. Elle le fait avec 10 fils d'exécution en parallèle. Le socket de chaque fil a un délai d'expiration de 1 seconde s'il ne reçoit pas de réponse. On peut donc s'attendre à ce que la tâche se termine en environ 100 secondes au maximum.

Un abonnement au comportement semblable, mis à part les conditions, est déjà intégré à Red Kite.

name: My complex subscription
findings:
- HostnameIpFinding
cooldown: 82800
job:
name: TcpPortScanningJob
parameters:
- name: targetIp
value: ${ip}
- name: threads
value: 10
- name: socketTimeoutSeconds
value: 1
- name: portMin
value: 1
- name: portMax
value: 1000
- name: ports
value: [1234, 3389, 8080]
conditions:
- lhs: ${ip}
operator: contains_i
rhs: "13.37"
- lhs: 5
operator: gte
rhs: 3

Plusieurs findings dans un abonnement​

Un abonnement sur événement peut être déclenché par plusieurs types de findings, à condition que les valeurs utilisées dans l'abonnement soient disponibles dans chacun de ces types.

Par exemple, un abonnement peut être déclenché à la fois par un HostnameIpFinding et par un IpFinding, et utiliser leurs champs communs.

Le HostnameIpFinding :

{
"type": "HostnameIpFinding",
"key": "HostnameIpFinding",
"domainName": "red-kite.io",
"ip": "0.0.0.0"
}

L'IpFinding :

{
"type": "IpFinding",
"key": "IpFinding",
"ip": "0.0.0.0"
}

Comme le HostnameIpFinding et l'IpFinding ont tous deux un champ ip, ce champ peut être utilisé dans l'abonnement.

name: My complex subscription
findings:
- HostnameIpFinding
- IpFinding
cooldown: 82800
job:
name: TcpPortScanningJob
parameters:
- name: targetIp
value: ${ip}
- name: threads
value: 10
- name: socketTimeoutSeconds
value: 1
- name: portMin
value: 1
- name: portMax
value: 1000
- name: ports
value: [1234, 3389, 8080]
conditions:
- lhs: ${ip}
operator: contains_i
rhs: "13.37"
- lhs: 5
operator: gte
rhs: 3

Findings​

Red Kite propage un événement de finding chaque fois qu'une nouvelle information entre en jeu. Chaque type de finding contient des informations qui lui sont propres.

Il est possible d'utiliser un finding produit par une tâche comme entrée d'une nouvelle tâche, ou comme opérande d'une condition. Les références aux variables de sortie d'un finding ne sont jamais sensibles à la casse.

Pour en savoir plus sur les findings, cliquez ici.

Conditions​

Par défaut, la tâche d'un abonnement sur événement est toujours lancée lorsque le finding indiqué est trouvé. Ce n'est toutefois pas toujours le comportement souhaité. Parfois, la tâche ne devrait démarrer que si la sortie du finding respecte certaines conditions.

Une condition se compose habituellement de trois éléments : un paramètre de gauche (lhs), un opérateur (operator) et un paramètre de droite (rhs). Le paramètre lhs est comparé au paramètre rhs à l'aide de l'opérateur. Une variable de sortie d'un finding peut servir de paramètre lhs ou rhs d'une condition lorsqu'elle est référencée avec la syntaxe ${paramName}.

Le type primitif des paramètres doit être le même des deux côtés et doit correspondre à l'opérateur pour que la condition retourne true. Les types acceptés pour un opérande sont string, number, boolean, ou un array de ces types. Lorsque deux tableaux sont fournis, chaque élément du tableau lhs est comparé à chaque élément du tableau rhs. Si toutes les conditions retournent vrai, la tâche indiquée est lancée.

OpérateurTypes d'opérandes acceptésDescription
gtenumberlhs >= rhs
gtnumberlhs > rhs
ltenumberlhs <= rhs
ltnumberlhs < rhs
equalsstring, number, booleanlhs == rhs
equals_istringInsensible à la casse, lhs == rhs
containsstringVérifie si la chaîne lhs contient la chaîne rhs.
contains_istringVérifie si la chaîne lhs contient la chaîne rhs. Insensible à la casse.
startsWithstringVérifie si la chaîne lhs commence par la chaîne rhs.
startsWith_istringVérifie si la chaîne lhs commence par la chaîne rhs. Insensible à la casse.
endsWithstringVérifie si la chaîne lhs se termine par la chaîne rhs.
endsWith_istringVérifie si la chaîne lhs se termine par la chaîne rhs. Insensible à la casse.
not_string, number, booleanAjoutez le préfixe not_ à un autre opérateur pour inverser le résultat. Par exemple, not_equals est vrai lorsque deux opérandes ne sont pas égaux.
or_string, number, booleanÀ utiliser avec des tableaux pour remplacer l'opérateur booléen entre les éléments du tableau, de and à or.

Des tableaux du type correspondant peuvent être utilisés avec n'importe quel opérateur.

Par défaut, les conditions sont liées par un opérateur and. Toutes les conditions doivent donc être remplies pour lancer la tâche. Vous pouvez toutefois utiliser les opérateurs and et or dans le yaml des conditions pour changer l'opérateur appliqué entre les conditions. Dans l'exemple suivant, même si la condition 4 >= 5 est false, elle se trouve dans un opérateur or : l'ensemble de cet opérateur retourne donc true. Comme l'opérateur or retourne true et que 1 <= 2 est aussi true, la tâche est lancée.

conditions:
- or:
- and:
- lhs: asdf
operator: endsWith
rhs: df
- lhs: qwerty
operator: startsWith
rhs: qwe
- lhs: 4
operator: gte
rhs: 5
- lhs: 1
operator: lte
rhs: 2

La condition précédente équivaut à peu près à l'instruction if Python suivante :

if (("qwerty".startswith("qwe") and "asdf".endswith("df")) or 4 >= 5) and 1 <= 2:
print("Job will start")