
C'est exact, aprÚs la sortie début mai 2019, Consul permet nativement l'authentification des applications et services exécutés sur Kubernetes.
Dans ce guide, nous allons crĂ©er Ă©tape par Ă©tape (Preuve de concept (Proof of concept, PoC) â dĂ©monstration de la faisabilitĂ© d'une idĂ©e â note du trad.) en montrant cette nouvelle fonctionnalitĂ©. Vous devez avoir des connaissances de base sur Kubernetes et Hashicorp's Consul. Bien que vous puissiez utiliser n'importe quelle plateforme cloud ou un environnement local, ce guide se concentre sur la Google Cloud Platform.
Aperçu
Si nous nous référons à , nous obtiendrons un aperçu de son objectif et de ses cas d'utilisation, ainsi que quelques détails techniques et un aperçu général de la logique. Je recommande vivement de la lire au moins une fois avant de continuer, car je vais tout expliquer et décomposer.

Schéma 1 : Vue d'ensemble officielle de la méthode d'authentification de Consul
Jetons un Ćil Ă .
Bien sĂ»r, il y a des informations utiles, mais il n'y a pas de guide sur la façon de rĂ©ellement mettre cela en Ćuvre. Par consĂ©quent, comme toute personne sensĂ©e, vous parcourrez Internet Ă la recherche d'un guide. Et puis... vous ĂȘtes en Ă©chec. Ăa arrive. Corrigeons cela.
Avant de passer à la création de notre POC, retournons à l'aperçu des méthodes d'authentification de Consul (Schéma 1) et précisons-le dans le contexte de Kubernetes.
Architecture
Dans ce guide, nous allons créer un serveur Consul sur une machine distincte qui interagira avec un cluster Kubernetes avec le client Consul installé. Ensuite, nous créerons notre application fictive dans un pod et utiliserons notre méthode d'authentification configurée pour lire depuis notre stockage clé/valeur Consul.
Le schéma ci-dessous montre en détail l'architecture que nous construisons dans ce guide, ainsi que la logique de la méthode d'authentification qui sera expliquée plus tard.

Schéma 2 : Vue d'ensemble de la méthode d'authentification dans Kubernetes
Une petite remarque : le serveur Consul n'a pas besoin de se trouver en dehors du cluster Kubernetes pour fonctionner. Mais oui, il peut aller et venir.
Donc, en prenant le schéma d'aperçu de Consul (Schéma 1) et en l'appliquant à Kubernetes, nous obtenons le schéma ci-dessus (Schéma 2), et la logique sera la suivante :
- Chaque pod sera associé à un compte de service contenant un jeton JWT, généré et connu de Kubernetes. Ce jeton est également intégré au pod par défaut.
- Notre application ou service Ă l'intĂ©rieur du pod initie une commande de connexion Ă notre client Consul. La requĂȘte de connexion inclura Ă©galement notre jeton et spĂ©cifiera le nom créé spĂ©cialement du type d'autorisation (type Kubernetes). Cette Ă©tape n° 2 correspond Ă l'Ă©tape 1 du schĂ©ma Consul (SchĂ©ma 1).
- Notre client Consul enverra ensuite cette requĂȘte Ă notre serveur Consul.
- MAGIE ! C'est ici que le serveur Consul vĂ©rifie l'authenticitĂ© de la requĂȘte, recueille des informations sur l'identitĂ© de la requĂȘte et les compare Ă des rĂšgles prĂ©dĂ©finies associĂ©es. Ci-dessous se trouve un autre schĂ©ma pour illustrer cela. Cette Ă©tape correspond aux Ă©tapes 3, 4 et 5 du schĂ©ma rĂ©capitulatif de Consul (SchĂ©ma 1).
- Notre serveur Consul génÚre un jeton Consul avec des autorisations selon les rÚgles que nous avons spécifiées pour l'identité du demandeur. Ensuite, il renverra ce jeton. Cela correspond à l'étape 6 du schéma Consul (Schéma 1).
- Notre client Consul redirige le jeton vers l'application ou le service demandeur.
Notre application ou service peut maintenant utiliser ce jeton Consul pour interagir avec nos données Consul, comme défini par les privilÚges du jeton.
La magie est révélée !
Pour ceux d'entre vous qui ne se contentent pas simplement d'un lapin sorti d'un chapeau et qui veulent savoir comment cela fonctionne⊠laissez-moi vous montrer à quel point la taniÚre du lapin».
Comme mentionnĂ© prĂ©cĂ©demment, notre Ă©tape "magique" (SchĂ©ma 2 : Ătape 4) est que le serveur Consul authentifie la requĂȘte, recueille des informations sur la requĂȘte et les compare Ă des rĂšgles prĂ©dĂ©finies associĂ©es. Cette Ă©tape correspond aux Ă©tapes 3, 4 et 5 du schĂ©ma rĂ©capitulatif de Consul (SchĂ©ma 1). Ci-dessous, un schĂ©ma (SchĂ©ma 3) a pour but d'illustrer ce qui se passe rĂ©ellement en arriĂšre-plan pour un type d'autorisation Kubernetes spĂ©cifique.

Schéma 3 : La magie est révélée !
- En point de dĂ©part, notre client Consul redirige la requĂȘte de connexion vers notre serveur Consul avec le jeton du compte Kubernetes et le nom spĂ©cifique de l'instance du type d'autorisation qui a Ă©tĂ© créé prĂ©cĂ©demment. Cette Ă©tape correspond Ă l'Ă©tape 3 dans l'explication prĂ©cĂ©dente du schĂ©ma.
- Le serveur Consul (ou le leader) doit maintenant vérifier l'authenticité du token reçu. Il consultera donc le cluster Kubernetes (via le client Consul) et, avec les permissions appropriées, nous déterminerons si le token est valide et à qui il appartient.
- La demande vérifiée est ensuite renvoyée au leader Consul, et le serveur Consul effectue une recherche de l'instance de la méthode d'authentification avec le nom spécifié dans la demande de connexion (et de type Kubernetes).
- Le leader Consul identifie l'instance de la méthode d'authentification spécifiée (si elle est trouvée) et lit l'ensemble des rÚgles d'attribution qui y sont attachées. Il examine ensuite ces rÚgles et les compare avec les attributs d'identité vérifiés.
- Et voilà ! Passons à l'étape 5 de l'explication précédente du schéma.
Lancez le serveur Consul sur une machine virtuelle classique.
à partir de maintenant, je vais principalement donner des instructions pour créer ce POC, souvent sous forme de points, sans phrases explicatives complÚtes. Comme mentionné précédemment, j'utiliserai GCP pour créer toute l'infrastructure, mais vous pouvez créer une infrastructure équivalente ailleurs.
- Démarrez la machine virtuelle (instance / serveur).

- Créez une rÚgle pour le pare-feu (groupe de sécurité sur AWS) :
- J'aime donner le mĂȘme nom Ă la machine qu'Ă la rĂšgle et au tag rĂ©seau, dans ce cas, c'est «skywiz-consul-server-poc».
- Trouvez l'adresse IP de votre ordinateur local et ajoutez-la à la liste des adresses IP sources afin que nous puissions accéder à l'interface utilisateur (UI).
- Ouvrez le port 8500 pour l'UI. Cliquez sur Créer. Nous allons bientÎt modifier à nouveau ce pare-feu [].
- Ajoutez une rÚgle pour le pare-feu à l'instance. Retournez au tableau de bord VM sur le serveur Consul et ajoutez «skywiz-consul-server-poc» dans le champ des tags réseau. Cliquez sur Enregistrer.

- Installez Consul sur la machine virtuelle, vérifiez ici. N'oubliez pas que vous avez besoin d'une version de Consul ℠1.5 [lien]
- CrĂ©ons un Consul Ă nĆud unique â la configuration est la suivante.
groupadd --system consul
useradd -s /sbin/nologin --system -g consul consul
mkdir -p /var/lib/consul
chown -R consul:consul /var/lib/consul
chmod -R 775 /var/lib/consul
mkdir /etc/consul.d
chown -R consul:consul /etc/consul.d- Un guide plus dĂ©taillĂ© sur l'installation de Consul et la configuration d'un cluster de 3 nĆuds est disponible ici. .
- Créez le fichier /etc/consul.d/agent.json comme suit []:
### /etc/consul.d/agent.json
{
"acl" : {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true
}
}- Lancez notre serveur Consul :
consul agent
-server
-ui
-client 0.0.0.0
-data-dir=/var/lib/consul
-bootstrap-expect=1
-config-dir=/etc/consul.d- Vous devriez voir beaucoup de sorties et finalement «⊠mise à jour bloquée par les ACL».
- Trouvez l'adresse IP externe du serveur Consul et ouvrez un navigateur avec cette adresse IP sur le port 8500. Assurez-vous que l'interface utilisateur s'ouvre.
- Essayez d'ajouter une paire clé/valeur. Cela devrait échouer. C'est parce que nous avons chargé le serveur Consul avec des ACL et interdit toutes les rÚgles.
- Retournez Ă votre shell sur le serveur Consul et lancez le processus en arriĂšre-plan ou de toute autre maniĂšre pour qu'il fonctionne et entrez la commande suivante :
consul acl bootstrap- Trouvez la valeur «SecretID» et retournez à l'interface utilisateur. Dans l'onglet «ACL», entrez l'identifiant de secret que vous venez de copier. Copiez le SecretID ailleurs, nous en aurons besoin plus tard.
- Ajoutez maintenant une paire clé/valeur. Pour cette POC, ajoutons ce qui suit : clé : «custom-ns/test_key», valeur : «Je suis dans le dossier custom-ns !»
Lancement d'un cluster Kubernetes pour notre application avec le client Consul en tant que Daemonset
- CrĂ©ez un cluster K8s (Kubernetes). Nous le crĂ©erons dans la mĂȘme zone que le serveur pour un accĂšs plus rapide, et donc nous pouvons utiliser le mĂȘme sous-rĂ©seau pour une connexion simple avec des adresses IP internes. Nous l'appellerons «skywiz-app-with-consul-client-poc».

- à titre de note, voici un bon guide que j'ai trouvé lors de la configuration de la POC du cluster Consul avec Consul Connect.
- Nous allons également utiliser le chart helm de Hashicorp avec un fichier de valeurs étendu.
- Installez et configurez Helm. Ătapes de configuration :
kubectl create serviceaccount tiller --namespace kube-system
kubectl create clusterrolebinding tiller-admin-binding
--clusterrole=cluster-admin --serviceaccount=kube-system:tiller
. /helm init --service-account=tiller
. /helm update- chart helm :
- Utilisez le fichier de valeurs suivant (notez que j'ai désactivé la plupart) :
### poc-helm-consul-values.yaml
global:
enabled: false
image: "consul:latest"
# Expose the Consul UI through this LoadBalancer
ui:
enabled: false
# Allow Consul to inject the Connect proxy into Kubernetes containers
connectInject:
enabled: false
# Configure a Consul client on Kubernetes nodes. GRPC listener is required for Connect.
client:
enabled: true
join: ["<PRIVATE_IP_CONSUL_SERVER>"]
extraConfig: |
{
"acl" : {
"enabled": true,
"default_policy": "deny",
"enable_token_persistence": true
}
}
# Minimal Consul configuration. Not suitable for production.
server:
enabled: false
# Sync Kubernetes and Consul services
syncCatalog:
enabled: false- Appliquez le chart helm :
. /helm install -f poc-helm-consul-values.yaml . /consul-helm - name skywiz-app-with-consul-client-poc- Lors de la tentative de démarrage, il aura besoin de permissions pour le serveur Consul, donc ajoutons-les.
- Notez la «plage d'adresses des pods» située sur le tableau de bord du cluster et retournez à notre rÚgle pour le pare-feu «skywiz-consul-server-poc».
- Ajoutez la plage d'adresses pour le pod Ă la liste des adresses IP et ouvrez les ports 8301 et 8300.

- AccĂ©dez Ă l'interface utilisateur de Consul, et aprĂšs quelques minutes, vous verrez que notre cluster apparaĂźtra dans l'onglet des nĆuds.

Configuration de la méthode d'autorisation en intégrant Consul avec Kubernetes
- Retournez dans le shell du serveur Consul et exportez le token que vous avez sauvegardé précédemment :
export CONSUL_HTTP_TOKEN=- Nous aurons besoin des informations de notre cluster Kubernetes pour créer une instance de la méthode d'authentification :
- kubernetes-host
kubectl get endpoints | grep kubernetes- kubernetes-service-account-jwt
kubectl get sa -consul-client -o yaml | grep "- name:"
kubectl get secret -o yaml | grep token:- Le token est encodé en base64, déchiffrez-le avec votre outil préféré []
- kubernetes-ca-cert
kubectl get secret -o yaml | grep ca.crt:- Prenez le certificat âca.crtâ (aprĂšs l'avoir dĂ©codĂ© depuis base64) et enregistrez-le dans le fichier âca.crtâ.
- Maintenant, créez une instance de la méthode d'authentification, en remplaçant les placeholders par les valeurs que vous venez d'obtenir.
consul acl auth-method create
-type "kubernetes"
-name "auth-method-skywiz-consul-poc"
-description "Ceci est une méthode d'authentification utilisant kubernetes pour le cluster skywiz-app-with-consul-client-poc"
-kubernetes-host ""
-kubernetes-ca-cert=@ca.crt
-kubernetes-service-account-
jwt=""- Ensuite, nous devons créer une rÚgle et l'associer au nouveau rÎle. Pour cette partie, vous pouvez utiliser l'interface utilisateur de Consul, mais nous allons utiliser la ligne de commande.
- Ăcrivez la rĂšgle
### kv-custom-ns-policy.hcl
key_prefix "custom-ns/" {
policy = "write"
}- Appliquez la rĂšgle
consul acl policy create
-name kv-custom-ns-policy
-description "Ceci est un exemple de politique pour kv dans custom-ns/"
-rules @kv-custom-ns-policy.hcl- Trouvez l'identifiant de la rÚgle que vous venez de créer dans la sortie.
- Créez un rÎle avec la nouvelle rÚgle.
consul acl role create
-name "custom-ns-role"
-description "Ceci est un exemple de rĂŽle pour l'espace de noms custom-ns"
-policy-id- Nous allons maintenant lier notre nouveau rĂŽle Ă l'instance de la mĂ©thode d'authentification. Notez que le flag « sĂ©lecteur » dĂ©termine si notre requĂȘte d'entrĂ©e obtiendra ce rĂŽle. Consultez ici d'autres options de sĂ©lecteur :
consul acl binding-rule create
-method=auth-method-skywiz-consul-poc
-bind-type=role
-bind-name='custom-ns-role'
-selector='serviceaccount.namespace=="custom-ns"'Configurations pour finir
Droits d'accĂšs
- Créez des droits d'accÚs. Nous devons donner à Consul la permission de vérifier et d'identifier le jeton d'identité du compte de service K8s.
- Ăcrivez dans le fichier ce qui suit :
###skywiz-poc-consul-server_rbac.yaml
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: review-tokens
namespace: default
subjects:
- kind: ServiceAccount
name: skywiz-app-with-consul-client-poc-consul-client
namespace: default
roleRef:
kind: ClusterRole
name: system:auth-delegator
apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: service-account-getter
namespace: default
rules:
- apiGroups: [""]
resources: ["serviceaccounts"]
verbs: ["get"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: get-service-accounts
namespace: default
subjects:
- kind: ServiceAccount
name: skywiz-app-with-consul-client-poc-consul-client
namespace: default
roleRef:
kind: ClusterRole
name: service-account-getter
apiGroup: rbac.authorization.k8s.io- Créons des droits d'accÚs
kubectl create -f skywiz-poc-consul-server_rbac.yamlConnexion au Client Consul
- Comme mentionné , il existe plusieurs options pour se connecter au daemonset, mais nous allons passer à la prochaine solution simple :
- Appliquez le fichier suivant [].
### poc-consul-client-ds-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: consul-ds-client
spec:
selector:
app: consul
chart: consul-helm
component: client
hasDNS: "true"
release: skywiz-app-with-consul-client-poc
ports:
- protocol: TCP
port: 80
targetPort: 8500- Ensuite, appliquez la commande intégrée suivante pour créer le configmap []. Notez que nous faisons référence au nom de notre service, remplacez-le si nécessaire.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
labels:
addonmanager.kubernetes.io/mode: EnsureExists
name: kube-dns
namespace: kube-system
data:
stubDomains: |
{"consul": ["$(kubectl get svc consul-ds-client -o jsonpath='{.spec.clusterIP}')"]}
EOFTest de la méthode d'authentification
Voyons maintenant la magie en action !
- CrĂ©ez quelques autres dossiers clĂ©s avec la mĂȘme clĂ© de niveau supĂ©rieur (c'est-Ă -dire /sample_key) et la valeur de votre choix. CrĂ©ez des politiques et des rĂŽles correspondants pour de nouveaux chemins clĂ©s. Nous ferons les liaisons plus tard.

Test utilisateur de l'espace de noms :
- Créons notre propre espace de noms :
kubectl create namespace custom-ns- CrĂ©ons un pod dans notre nouvel espace de noms. Ăcrivez la configuration pour le pod.
###poc-ubuntu-custom-ns.yaml
apiVersion: v1
kind: Pod
metadata:
name: poc-ubuntu-custom-ns
namespace: custom-ns
spec:
containers:
- name: poc-ubuntu-custom-ns
image: ubuntu
command: ["/bin/bash", "-ec", "sleep infinity"]
restartPolicy: Never- Créer un pod :
kubectl create -f poc-ubuntu-custom-ns.yaml- Une fois le conteneur démarré, entrez à l'intérieur et installez curl.
kubectl exec poc-ubuntu-custom-ns -n custom-ns -it /bin/bash
apt-get update && apt-get install curl -y- Nous allons maintenant envoyer une requĂȘte de connexion Ă Consul, en utilisant la mĂ©thode d'autorisation que nous avons créée prĂ©cĂ©demment [].
- Pour afficher le jeton saisi de votre compte de service :
cat /run/secrets/kubernetes.io/serviceaccount/token- Ăcrivez ce qui suit dans un fichier Ă l'intĂ©rieur du conteneur :
### payload.json
{
"AuthMethod": "auth-method-test",
"BearerToken": "<jwt_token>"
}- Connexion !
curl
--request POST
--data @payload.json
consul-ds-client.default.svc.cluster.local/v1/acl/login- Pour effectuer les étapes ci-dessus en une seule ligne (puisque nous ferons plusieurs tests), vous pouvez faire ce qui suit :
echo "{
"AuthMethod": "auth-method-skywiz-consul-poc",
"BearerToken": "$(cat /run/secrets/kubernetes.io/serviceaccount/token)"
}"
| curl
--request POST
--data @-
consul-ds-client.default.svc.cluster.local/v1/acl/login- Ăa marche ! Du moins, ça devrait. Prenez maintenant le SecretID et essayez d'accĂ©der Ă la clĂ©/valeur Ă laquelle nous devrions avoir accĂšs.
curl
consul-ds-client.default.svc.cluster.local/v1/kv/custom-ns/test_key --header "X-Consul-Token: "- Vous pouvez dĂ©coder la «Value» en base64 et voir qu'elle correspond Ă la valeur dans custom-ns/test_key dans l'UI. Si vous avez utilisĂ© la mĂȘme valeur mentionnĂ©e ci-dessus dans ce guide, votre valeur codĂ©e sera IkknbSBpbiB0aGUgY3VzdG9tLW5zIGZvbGRlciEi.
Test du compte de service personnalisé :
- Créez un compte de service personnalisé avec la commande suivante [].
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: custom-sa
EOF- CrĂ©ez un nouveau fichier de configuration pour le pod. Notez que j'ai inclus l'installation de curl pour vous faciliter la tĂąche đ
###poc-ubuntu-custom-sa.yaml
apiVersion: v1
kind: Pod
metadata:
name: poc-ubuntu-custom-sa
namespace: default
spec:
serviceAccountName: custom-sa
containers:
- name: poc-ubuntu-custom-sa
image: ubuntu
command: ["/bin/bash","-ec"]
args: ["apt-get update && apt-get install curl -y; sleep infinity"]
restartPolicy: Never- AprÚs cela, lancez un shell à l'intérieur du conteneur.
kubectl exec -it poc-ubuntu-custom-sa /bin/bash- Connexion !
echo "{
"AuthMethod": "auth-method-skywiz-consul-poc",
"BearerToken": "$(cat /run/secrets/kubernetes.io/serviceaccount/token)"
}"
| curl
--request POST
--data @-
consul-ds-client.default.svc.cluster.local/v1/acl/login- Permission denied. Oops, nous avons oublié d'ajouter un nouveau binding de rÚgle avec les autorisations appropriées, faisons-le maintenant.
Répétez les étapes précédentes ci-dessus :
a) Créez une Politique identique pour le préfixe «custom-sa/».
b) Créez un RÎle, nommez-le «custom-sa-role»
c) Attachez la Politique au RĂŽle.
- Créez un Rule-Binding (possible uniquement depuis cli / api). Notez la valeur différente du drapeau de sélection.
consul acl binding-rule create
-method=auth-method-skywiz-consul-poc
-bind-type=role
-bind-name='custom-sa-role'
-selector='serviceaccount.name=="custom-sa"'- Reconnectez-vous depuis le conteneur «poc-ubuntu-custom-sa». SuccÚs !
- Vérifiez notre accÚs au chemin clé custom-sa/.
curl
consul-ds-client.default.svc.cluster.local/v1/kv/custom-sa/test_key --header âX-Consul-Token: â- Vous pouvez Ă©galement vĂ©rifier que ce token n'accorde pas d'accĂšs au kv dans «custom-ns/». Il suffit de rĂ©pĂ©ter la commande ci-dessus aprĂšs avoir remplacĂ© «custom-sa» par le prĂ©fixe «custom-ns».
Permission denied.
Exemple d'overlay :
- Il convient de noter que tous les bindings de rÚgle seront ajoutés au token avec ces droits.
- Notre conteneur «poc-ubuntu-custom-sa» se trouve dans l'espace de noms par dĂ©faut â utilisons-le donc pour un autre binding de rĂšgle.
- Répétez les étapes précédentes :
a) Créez une Politique identique pour le préfixe de clé «default/».
b) Créez un RÎle, nommez-le «default-ns-role»
c) Attachez la Politique au RÎle. - Créez un Rule-Binding (possible uniquement depuis cli / api)
consul acl binding-rule create
-method=auth-method-skywiz-consul-poc
-bind-type=role
-bind-name='default-ns-role'
-selector='serviceaccount.namespace=="default"'- Retournez vers notre conteneur «poc-ubuntu-custom-sa» et essayez d'accéder au chemin «default/» kv.
- Permission denied.
Vous pouvez visualiser les informations d'identification spécifiées pour chaque token dans l'interface utilisateur sous ACL > Tokens. Comme vous pouvez le voir, à notre token actuel est attaché seulement un «custom-sa-role». Le token que nous utilisons actuellement a été généré lors de la connexion, et il n'y avait alors qu'un seul binding de rÚgle qui correspondait. Nous devons nous reconnecter et utiliser un nouveau token. - Assurez-vous que vous pouvez lire à partir des chemins «custom-sa/» et «default/» kv.
SuccĂšs !
Cela est dû au fait que notre «poc-ubuntu-custom-sa» correspond aux bindings de rÚgle «custom-sa» et «default-ns».
Conclusion
Gestion du TTL du token ?
Au moment de la rĂ©daction de cet article, il n'existe pas de moyen intĂ©grĂ© pour dĂ©terminer le TTL des tokens gĂ©nĂ©rĂ©s par cette mĂ©thode d'autorisation. Ce serait une possibilitĂ© fantastique â assurer l'automatisation sĂ©curisĂ©e de l'autorisation dans Consul.
Il est possible de créer manuellement un jeton avec un TTL :
Expiration Time (Heure d'expiration) â le moment oĂč ce jeton sera rĂ©voquĂ©. (Facultatif ; ajoutĂ© dans Consul 1.5.0)- Disponible uniquement pour la crĂ©ation / mise Ă jour manuelle
J'espÚre qu'à brÚve échéance, nous pourrons contrÎler comment les jetons sont générés (pour chaque rÚgle ou méthode d'autorisation) et ajouter un TTL.
D'ici là , il est conseillé d'utiliser un point de sortie dans sa logique.
Lisez aussi d'autres articles sur notre blog :
Source : habr.com
