Affichage des articles dont le libellé est java. Afficher tous les articles
Affichage des articles dont le libellé est java. Afficher tous les articles

samedi 28 janvier 2017

Chiffrement/déchiffrement des mots de passe de vos scripts gérés avec Ansible


Pour les paresseux, un exemple d'implémentation est disponible à l'adresse suivante : https://github.com/Yannig/yannig-ansible-playbooks/tree/master/scripts-vault

Mais pourquoi chiffrer d'abord ?

L'automatisation c'est bien mais des fois vous pouvez rencontrer quelques petits problèmes. Un que je rencontre régulièrement vient de la présence de mot de passe en clair dans mes scripts d'administration WebLogic/Oracle etc. Pour comprendre le problème, prenons l'extrait de playbook Ansible suivant :

- name: "Dépot d'un template de création"
  template:
    src: "template.sh.j2"
    dest: "/tmp/creation.sh"
    mode: "755"
  register: _

- name: "Exécution du template"
  shell: "/tmp/creation.sh"
  when: _.changed|d('no')|bool

Le déroulement est assez simple : on dépose un template sur la machine et on exécute le script si ce dernier a changé (suite à une mise à jour ou simplement parce que le script n'existait pas). Problème : si ce script contient un mot de passe, il est en clair sur la machine ce qui ne fait pas très sérieux.

Nous allons voir comment faire en sorte que les mots de passe soient à disposition sur la machine distante au moment de leur exécution sans pour autant les mettre en clair ou en tout cas rendre très difficile leur exploitation. Pour cela, nous passerons par une clé de chiffrement dans une variable d'environnement et nous verrons comment récupérer ça depuis différents langages (ici Python, Java et shell Unix).

Utilisation du 3DES pour chiffrer

Le principe va être le suivant : on génère une clé de chiffrement sur 24 caractères, nous chiffrons le mot de passe avec et nous la mettons à disposition sur la machine distante que nous déchiffrons via l'utilisation d'une variable d'environnement.

Cette clé peut-être positionné dans votre inventaire ou via un générateur de clé avec une graine connue (seed). Cette graine peut venir du nom de votre serveur, une variable déjà existante propre à votre plateforme, etc. J'en parlerai sûrement une prochaine fois pour voir ce qu'il est possible de faire.

Mais revenons à nos moutons. Première chose à faire : créer le programme qui nous permettra de faire le chiffrement à partir de cette clé. Ci-dessous un programme python permettant de faire du chiffrement 3DES :

import sys, os, base64
from Crypto.Cipher import DES3

BS    = 8
pad   = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 

key = os.getenv('DES_KEY')
des = DES3.new(key, DES3.MODE_ECB)
print(base64.b64encode(des.encrypt(pad(sys.argv[1]))))

La clé de chiffrement se trouve stockée dans la variable d'environnement DES_KEY. Ci-dessous un exemple d'utilisation de ce mécanisme :

$ DES_KEY=111111111111111111111111 ./chiffre.py abcdefghijklmnopqrstuvwxyz
eHPtqHbKD+oGdodkRUxdwo8Z4EcsTUBrrSeCYcBIQ0g=

Relançons notre programme en changeant notre clé :

$ DES_KEY=111111111111111111111112 ./chiffre.py abcdefghijklmnopqrstuvwxyz
vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos=

Parfait, on voit bien que le chiffrement de notre mot de passe change complètement lorsqu'on change la clé.

Déchiffrons notre message

Nous arrivons à chiffrer. Écrivons maintenant le petit bout de code python qui va nous permettre de récupérer le mot de passe :

import sys, os, base64
from Crypto.Cipher import DES3

BS    = 8
unpad = lambda s: s[0:-ord(s[-1])]

key = os.getenv('DES_KEY')
des = DES3.new(key, DES3.MODE_ECB)
print(unpad(des.decrypt(base64.b64decode(sys.argv[1]))))

Faisons maintenant quelques tests :

$ DES_KEY=111111111111111111111112 ./dechiffre.py vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos=
abcdefghijklmnopqrstuvwxyz

Youpi ! On récupère notre mot de passe !

Maintenant que nous avons notre code python, voyons comment récupérer ce mot de passe depuis un autre langage.

Déchiffrement Java

Si comme moi vous n'avez pas été sage et que vous faites de l'administration Java, vous aurez peut-être besoin de récupérer ce mot de passe depuis un programme en Java. Le bout de code suivant devrait nous aider à réaliser cette opération :

// Récupère un mot de passe (ou autre chose) chiffré avec une clé 3DES et renvoie
// la chaîne à l'aide de la clé contenue dans la variable d'environnement DES_KEY

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

// Decrypt a password
class dechiffre {
  public static void main(String [] args) throws Exception {
    System.out.println(decrypt(args[0]));
  }
  public static String decrypt(String password) throws Exception {
    String env_key = System.getenv("DES_KEY");

    Cipher out_cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
    SecretKeySpec key = new SecretKeySpec(env_key.getBytes(), "DESEDE");
    out_cipher.init(Cipher.DECRYPT_MODE, key);
    return new String(out_cipher.doFinal(Base64.getDecoder().decode(password)), "UTF-8");
  }
}

Notons au passage la qualité première de Java : sa concision.

Lançons maintenant notre petit programme et voyons ce que ça donne :

$ DES_KEY=111111111111111111111112 java dechiffre vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos=
abcdefghijklmnopqrstuvwxyz

C'est pas trop mal. Voyons ce que ça donne en changeant la clé :

DES_KEY=111111111111111111111111 java dechiffre vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos=
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded
       at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:975)
       at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:833)
       at com.sun.crypto.provider.DESedeCipher.engineDoFinal(DESedeCipher.java:294)
       at javax.crypto.Cipher.doFinal(Cipher.java:2165)
       at dechiffre.decrypt(dechiffre.java:19)
       at dechiffre.main(dechiffre.java:11)


Magnifique, une superbe stacktrace Java pour nous indiquer que ça ne fonctionne pas bien !

Et si on faisait la même chose en shell ?

Pas mal mais j'écris rarement mes programmes de création d'environnement en Java. Je me suis donc posé la question s'il serait possible de faire la même chose en shell. En effet, j'ai régulièrement besoin de déposer des scripts shell afin de procéder à la création d'objets divers avec des morceaux de mot de passe dedans. Là aussi, j'aurai bien aimé pouvoir chiffrer mes mots de passe sur la machine distante.

Cette fois ci, la solution vient d'openssl. Petit bonus, il s'agit d'un programme relativement courant à trouver sur une machine Linux (sauf si vous êtes sur une image Docker où en général il est supprimé). Nous allons voir comment l'utiliser dans notre contexte :

$ export DES_KEY=111111111111111111111112
$ echo vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos= | \
    openssl des-ede3 -d -a -K $(echo -n "$DES_KEY" | xxd -pu)
abcdefghijklmnopqrstuvwxyz

A noter que j'ai un peu tâtonné pour trouver le bon algo (ici des-ede3). En effet, avec l'algo des3, ça fonctionnait presque mais ça plantait sur des chaînes de plus de 8 caractères. Autre problème : openssl veut une chaîne au format hexadécimal. Il faut donc faire cette transformation avec la chaîne de la clé avec xxd (et l'option -pu). Autre point d'attention lors de la transformation : faire attention à ne pas rajouter de retour à la ligne à xxd sinon vous fausserez votre clé.

En dehors de ça, mission accomplie, nous avons notre principe.

Pour conclure

Nous avons vu comment faire le chiffrement reste maintenant à combiner ça avec votre outil de gestion de conf préféré (Ansible, Puppet etc.). L'astuce consistera à passer la variable d'environnement au moment du lancement du script. Ci-dessous un exemple de passage de variable :

- name: "Dépot script"
  template:
    scr: "test.sh.j2"
    dest: "/tmp/monscript.sh"
  vars:
    encrypted_value: "vD37nk4ME9n5BUa9pLH0zrHGWl2xUkLRlLhEwfkFmos="
- name: "Rafraichir configuration"
  shell: "/tmp/monscript.sh"
  environment:
    DES_KEY: "{{des_key}}"

Ci-dessous le contenu du template test.sh.j2 :

#!/bin/bash
mdp=$(echo {{encrypted_value}} | openssl des-ede3 -d -a -K $(echo -n "$DES_KEY" | xxd -pu) &> /dev/null) && echo $mdp

Depuis Ansible, tout va bien, le shell se lance bien. Essayons de lancer maintenant ce script depuis la machine :

$ ./test.sh

Le script n'affiche rien. Si on vérifie la valeur du code retour, on se rend compte qu'il y a eu un problème lors du lancement du script :
$ echo $?
1

Parfait ! Nous sommes incapable d'obtenir le mot de passe directement depuis la machine : mission accomplie ! Reste maintenant à généraliser ce mécanisme.

Petite astuce au passage : passer par l'utilisation de filtre Ansible pour gérer le chiffrement de vos mots de passe. Ça simplifiera grandement votre travail.

mercredi 3 octobre 2012

Mise en place selenium

Ces derniers temps, on m'a demandé de mettre en place de scénario de surveillance sur une application typiquement horrible : code HTML horrible, Internet Explorer only etc.

Bref, j'ai donc commencé à vouloir faire ça en cucumber mais je me suis rapidement frotté à des problèmes mon cher concombre masqué refusant catégoriquement mes noms de formulaires fantaisiste (il faut dire qu'on trouve des dollars ('$') dedans ...).

Bien triste, j'ai commencé à chercher quelque chose d'autre qui pourrait répondre à mon besoin et je suis tombé sur selenium (http://seleniumhq.org/) et son IDE (qui est une simple extension à installer dans firefox).

J'ai commencé à faire joujou avec l'IDE et - malgré un rendu HTML catastrophique - j'ai réussi à faire un scénario de test avec Firefox.

En trifouillant un peu, j'ai vu qu'il existait un serveur selenium (un process java) et qu'il pouvait tourner dans un serveur X virtuel. Devant tant de beauté, j'ai donc commencé à mettre ça en place :D.

Pré-requis python

Comme par la suite je vais générer des scripts au format python, nous allons devoir installer la librairie python permettant de communiquer avec selenium. Ici, rien de bien compliqué, un coup de apt-get pour installer le nécessaire :

apt-get install python-pip python-dev build-essential

Maintenant que nous avons pip, nous pouvons procéder à l'installation de la librairie selenium :

pip install -U selenium

Passons maintenant à la suite (et y'a du monde !)

X11 Virtual frame buffer (miam !)

Ce cher Xvfb ! Toujours un petit moment émouvant quand je le croise ! Pour le lancer, il faut simplement utiliser la ligne de commande suivante :

DISPLAY=:1 Xvfb :1 -screen 0 1024x768x24

Bien penser à le rendre utilisable depuis n'importe où avec la commande xhost :

DISPLAY=:1 xhost +

ATTENTION : L'utilisation de xhost + va rendre le serveur X visible depuis l'extérieur !!! Il est conseillé de mettre en place un parefeu pour bloquer les accès externes.

L'installation se fait avec un apt-get install xvfb (et x11-server-utils pour disposer de l'utilitaire xhost)

Installation de Firefox

Par la suite, nous utiliserons firefox pour faire nos tests. Donc rien de bien fameux si ce n'est un apt-get install firefox (à remplacer par iceweasel si vous êtes sur Debian).

Mise en place de Selenium

On arrive enfin au coeur du sujet. Selenium est un serveur qui s'appuie sur un jar java pour se lancer. Donc de manière logique, il va vous falloir un java fonctionnel. Je vous passe les détails, vous faîtes comme vous voulez.

Récupérer ensuite la dernière version de selenium serveur sous la forme d'un JAR. Vous pouvez ensuite le lancer avec la ligne de commande suivante :

export DISPLAY=:1
java -Djava.security.egd=file:/dev/./urandom \
     -jar /path/to/jar/selenium-server-standalone-2.25.0.jar \
     -browserSessionReuse

A noter la présence de l'option -browserSessionReuse qui permet d'éviter de relancer le navigateur à chaque lancement de scénario et l'option biscornu java.security.egd qui permet de passer du device aléatoire /dev/random (qui est lent) à /dev/urandom qui lui n'utilise pas l'entropie du système pour fonctionner mais un générateur de nombre aléatoire.

Test de notre premier scénario

À l'aide de l'IDE selenium, nous avons enregistré notre scénario. Nous allons maintenant exporter ce dernier au format Python 2 / unittest / Remote Control. Voici ce à quoi ça devrait ressembler :

from selenium import selenium
import unittest, time, re

class sites(unittest.TestCase):
    def setUp(self):
        self.verificationErrors = []
        self.selenium = selenium("localhost", 4444, "*chrome", "http://monsite/")
        self.selenium.start()
    
    def test_sites(self):
        sel = self.selenium
        sel.open("/ma_page")
        sel.type("id=un_champ", "une_valeur")
        sel.type("id=un_autre_champ", "une_autre_valeur")
        sel.click("id=id_d_un_lien")
        sel.wait_for_page_to_load("30000")
    
    def tearDown(self):
        self.selenium.stop()
        self.assertEqual([], self.verificationErrors)

if __name__ == "__main__":
    unittest.main()
Lançons maintenant ce beau script depuis la machine faisant tourner selenium :

python mon_scenario.py
.
----------------------------------------------------------------------
Ran 1 test in 4.051s
OK

Magnifique non ? Tout à fait mais ça ne vous dit pas ce que vous allez en faire dans votre nagios.

(Des)intégrations dans nagios/Shinken

Comment ça, vous voulez le script qui analyse la sortie de tout ça et l'insert directement dans nagios ? Je vous trouve un peu exigeant quand même !

Bon, si vous insistez ... Il faut récupérer le script check_selenium.pl et lancer ensuite la sonde de la manière suivante :

/emplacement/scripts/check_selenium.pl --script \
      /emplacement/de/mon/scenario.py  \
      --label "Le test de ma super application"

De là, vous devriez obtenir la sortie suivante :

Selenium test OK (Le test de ma super application)|time=3.180s test_count=1

Elle est pas belle la vie ?

lundi 11 avril 2011

mod_proxy_http Apache et console de gestion du load-balancer

J'ai eu à faire une petite étude entre deux type de load-balancing HTTP sur des serveurs d'application WebLogic et c'est à cette occasion que j'ai découvert une perle : la console apache de load-balancing.

Je vous propose de voir ensemble comment l'activer et ce qu'elle vous apporte.

Par la suite je travaillerai avec une RedHat Enterprise 5.3 mais le principe reste le même pour n'importe quelle distribution.

Activation du module mod_proxy_http


Premier point, il vous faut installer une version récente de apache 2.2 et vérifier que la conf de ce dernier fait bien référence au module mod_proxy_http :

[root@myserver conf]# pwd
/etc/httpd/conf
[root@myserver conf]# grep mod_proxy_http.so httpd.conf
LoadModule proxy_http_module modules/mod_proxy_http.so

Un petit coup de apachectl configtest nous assurera que nous n'avons rien oublié.

Création d'une boucle de charge


Placez-vous ensuite dans le répertoire conf de votre serveur apache et créez-y un nouveau fichier de configuration contenant les lignes suivantes (à adapter suivant vos besoins) :

<Proxy balancer://lb-http>
  BalancerMember http://127.0.0.1:7002
  BalancerMember http://127.0.0.1:7004
</Proxy>

ProxyPass        /sample/ balancer://lb-http/sample/
ProxyPassReverse /sample/ balancer://lb-http/sample/

Ici, les URLs sous le contexte /sample/ seront envoyées systématiquement vers nos deux serveurs d'applications Java hébergeant l'application sample.war.

Un arrêt/relance à l'aide de la commande apachectl restart et voici notre configuration pris en compte.

Activation de la console du load-balancer


Activons maintenant la console du load-balancer en ajoutant les lignes suivantes :

<Location /balancer-manager>
  SetHandler balancer-manager
  Order Deny,Allow
  # Deny from all    <- Decommenter ici pour bloquer les accès
  Allow from all #   <- lister les IPs autorisées en les séparant par des virgules
</Location>

Un arrêt/relance sera bien sûr nécessaire pour en profiter.

Utilisation de la console du load-balancer


Il suffit maintenant de rentrer l'URL de notre serveur apache suivi de /balancer-manager pour obtenir l'écran suivant :


De là, vous pourrez désactiver ou réactiver une instance en cliquant sur l'instance à modifier. L'écran en question est le suivant :


Il vous permettra également de modifier certains paramètres de poids sur vos serveurs HTTP/Java.

Pour finir ...


Le gros avantage de ce mécanisme est que vous pourrez ainsi désactiver une instance à la demande et pouvoir ainsi procéder à une maintenance, une mise à jour etc. en limitant au maximum les perturbations.

Pour les plus curieux, je n'ai pas d'autres conseils que d'aller consulter la documentation apache de ce module disponible à l'adresse suivante : http://httpd.apache.org/docs/current/mod/mod_proxy.html.