Accueil / Blog / Métier / 2015 / Chiffrer les mots de passe dans les fichiers de configuration avec Talend

Chiffrer les mots de passe dans les fichiers de configuration avec Talend

Par Gaël Pegliasco publié 19/12/2015, édité le 21/12/2015
Une des bonnes pratiques de Talend pour faciliter le paramétrage de vos jobs consiste à stocker les paramètres de contexte dans un fichier de configuration de type « .properties » puis à charger ces paramètres au démarrage du job. Mais le fonctionnement par défaut sauvegarde les mots de passe en clair. Ce tutoriel vous présente 6 manières de chiffrer ces derniers et seulement ceux-ci.
Chiffrer les mots de passe dans les fichiers de configuration avec Talend

Terminologie

Un brin de terminologie avant de commencer : 

  • Le mot crypter n'existe pas dans monde de la cryptographie.
  • On chiffre un message dont on est l'émetteur et que l'on souhaite adresser à un tiers afin que lui seul puisse le lire.
  • On déchiffre un message chiffré qui nous est adressé et pour lequel nous possédons les moyens normaux de lecture, généralement la clef permettant de le déchiffrer.
  • On décrypte un message chiffré qui ne nous est pas adressé et pour lequel nous ne possédons pas les moyens. d'appliquer la procédure normale de déchiffrement/lecture. Généralement c'est que nous n'en connaissons pas la clef.

Mais on peut chiffrer sans clef. Dans ce cas la sécurité du message repose sur la seule connaissance de l'algorithme, ce qui est rare aujourd'hui.

Lecture des paramètres de contexte depuis un fichier de configuration

C'est effectivement une des bonnes pratiques préconisée par Talend et proposée par ce logiciel. Lorsque vous appliquez ce procédé le chargement des paramètres peut se faire :

  • soit via le composant tContextLoad ;

  • soit via l'option « tContextLoad implicite » située au niveau de l'onglet job et des propriétés du projet. 

Voici un exemple d'un tel fichier de configuration contenant les paramètres de connexion à une base de données MySQL

#
#Wed Dec 16 18:23:03 CET 2015
MySQL_AdditionalParams=noDatetimeStringSync\=true
MySQL_Port=3306
MySQL_Login=root
MySQL_Password=secret_a_chiffrer
MySQL_Database=talend
MySQL_Server=localhost

La problématique

La problématique de cette solution réside sur le fait que les mots de passe sont stockés en clair dans le fichier de configuration. Cela n'est pas l'idéal en terme de sécurité.

Toutefois certains vous répliqueront que ce procédé ne constitue pas forcément un risque important en soi : bien souvent si l'accès à la machine est rigoureusement contrôlé, il suffit de donner les droits de lecture de ce fichier au seul utilisateur pouvant exécuter les jobs.

Cela reste cependant très sommaire comme protection et inacceptable en regard des critères de sécurité de nombreuses entreprises. Et elles ont bien raison.

Le problème que nous vous proposons d'étudier consiste donc à chiffrer les mots de passe contenus dans un fichier de configuration, et à chiffrer uniquement ceux-ci.

Les solutions

La version 6 de Talend n'est pas très généreuse pour répondre à notre besoin ; elle ne propose qu'un seul composant permettant de déchiffrer un fichier via PGP : « tGPGDecrypt ».

Cela ne répond pas à notre problématique car nous souhaitons uniquement chiffrer certaines informations du fichier de configuration et non tout le fichier.

La forge Talend est par contre bien plus avenante et propose plusieurs composants permettant de mener à bien ces tâches.

Nous vous présenterons donc dans ce tutoriel six jobs permettant de traiter ce problème, avec pour chacun plus ou moins de contraintes, de facilité et de succès.

  1. Le premier se contente de faire de l’obscurcissement de mot de passe en l'encodant en base64, via un brin de code Java.

  2. Le second fait la même chose avec le composant tBase64 de la forge, c'est moins facile.

  3. Le troisième utilise les composants tDecryptColumn et tEncryptColumn de la forge.

  4. Le quatrième, la bibliothèque native du JDK: JavaXCrypto.

  5. Le cinquième, proposé en 2 versions, utilisant la bibliothèque Java Jasypt pour chiffrer et déchiffrer les données.

  6. Le sixième la solution « password Store » de la forge.

Dans l'ensemble des jobs présentés nous considérons que tous les paramètres devant être chiffrés contiennent tous le mot « Password » dans leur nom.

Ceci nous permet de différencier les paramètres du fichier de configuration devant faire l'objet de notre attention.

Solution 1 : Simple obscurcissement base64

La première solution présentée permet de réaliser un simple obscurcissement du mot de passe en l'encodant en base64.

Ce n'est pas du chiffrement, n'importe qui peut le décoder en deux lignes de n'importe quel langage ; mais si vous avez juste besoin de brouillage c'est très efficace car vous n'avez pas besoin de conserver quelque part une « passphrase » permettant de chiffrer/déchiffrer vos données. C'est une opération bijective qui ne nécessite rien d'autre que la donnée à traiter.

L'offuscation via MD5 ou SHA est à proscrire : autant vous pourrez générer une empreinte MD5/SHA de votre mot de passe, autant vous ne serez pas capable de retrouver le vrai mot de passe à partir de cette empreinte : ces fonctions ne sont pas bijectives.

scenario-base64-1.png

Le premier sous-job de ce traitement enregistre les paramètres de contexte dans le fichier de configuration en chiffrant le mot de passe.

Le second permet de le lire le fichier de configuration, de déchiffrer le mot de passe puis d'initialiser les valeurs des paramètres de contexte.

Encodage

  • Le composant tContextDump génère un flux contenant les paramètres de contexte et leurs valeurs, un couple (paramètre, valeur) par enregistrement.

  • Le composant tJavaRow à la charge d'encoder les paramètres de type « mot de passe » et de laisser les autres en clair.

  • Enfin, le tFileOutputProperties sauvegarde ces paramètres dans un fichier de configuration.

Le code du tJavaRow fait appel à la classe Base64 de Java 8.

Quelques modifications seront nécessaires en Java 7.

Il contient le code suivant :

output_row.key = input_row.key;

if
(input_row.key.toLowerCase().contains("password") ){
Encoder enc = Base64.getEncoder();
output_row.value = enc.encodeToString( input_row.value.getBytes() );
}
else {
output_row.value = input_row.value;
}

Enfin, il convient d'importer les bibliothèques nécessaires dans les paramètres avancés du tJavaRow :

import java.util.Base64;
import
java.util.Base64.Encoder;

C'est tout simple, l'encodage en base64 se fait en 2 lignes de code Java.

Décodage

Le décodage est quasiment aussi simple.

Nous y avons ajouté 2 tLogRow afin de visualiser le flux avant et après décodage.

  • Le composant tFileOutputProperties lit le même fichier que celui généré par le tFileOutputProperties.

  • Le composant tJavaRow décode les mots de passe.

  • Et le tContextLoad initialise les paramètres de contexte avec ces valeurs.

Le contenu du tJavaRow est le suivant :

output_row.key = input_row.key;
if
(input_row.key.toLowerCase().contains("password") ){
  Decoder dec = Base64.getDecoder();
  output_row.value =
new String( dec.decode( input_row.value ) );
}
else {
  output_row.value = input_row.value;
}

Sa partie « avancée » contient les imports suivants :

import java.util.Base64.Decoder;
import
java.util.Base64;

Le résultat ressemble à ceci :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |c2VjcmV0 |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
 
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Solution 2 : Encodage/Décodage base64 via le composant tBase64

Si vous n'aimez pas programmer en Java vous pouvez toujours utiliser le composant tBase64 proposé sur la forge, mais là, le job se complique sévèrement car ce composant encode tous les enregistrements du flux, et pas seulement les mots de passe…

scenario-tbase64-1.png

Aussi, nous l'avons décomposé en 2 étapes.

La première, ci-dessus, encode les mots de passe à l'aide du tFilterRow qui a pour responsabilité de les sélectionner.

scenario-tbase64-2.png

 Puis le tBase64 réalise l'encodage de la colonne value.

scenario-tbase64-3.png

Les 2 composants tBufferOutput permettent de fusionner dans la mémoire tampon les données issues des 2 flux du tFilterRow, ils n'ont pas de paramétrage particulier.

Enfin, la seconde partie de cette première étape permet d'enregistrer le flux final via le composant tBufferInput qui restitue les données collectées par les 2 composants tBufferOutput.

Vous allez dire, pourquoi ne pas avoir utilisé le composant tUnite pour fusionner les 2 flux issus du tFilterRow ?

La réponse est que ce n'est pas possible. Le tUnite ne permet pas de fusionner 2 flux issus d'un même composant parent. C'est bien dommage.

Une dernière explication est encore nécessaire : nous utiliserons de nouveau les composants tBufferOutput pour lire le fichier de configuration. Or ceux-ci accumulent les données dans le même buffer que leurs deux collègues.
Afin de ne pas avoir une double sortie des paramètres (une fois pour leur sauvegarde et une autre fois pour leur lecture), nous utilisons un tJava pour vider le contenu du buffer, lequel est stocké dans une variable globale nommée : « globalBuffer ».

Le tJava contient donc cette seule ligne :

globalBuffer.clear();

 Le seconde partie du job, ci-dessous, lit le fichier de configuration et décode les mots de passe.

Nous avons réutilisé le tBase64, mais cette fois avec l'option « décode » pour la colonne value.

 scenario-tbase64-4.png

Le composant tFilterRow est identique. Le tBase64 utilise l'option « decode » le reste est classique.

Le résultat donne ceci :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |c2VjcmV0 |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
 
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Évidemment cette solution n'est pas la meilleure et nous vous conseillons vivement l'utilisation de la première version proposée utilisant un bout de code Java.

Elle permet toutefois de mesurer le gain de simplicité qu'apportent les deux lignes de code du composant tJavaRow utilisé dans la première solution.

Enfin, elle montre comment manipuler le tBufferOutput, qui est bien pratique et ne requiert aucune configuration.

Solution 3 : tEncryptColumn et tDecryptColumn

La forge Talend propose deux autres composants permettant de chiffrer et déchiffrer les colonnes de vos enregistrements, respectivement tEncryptColumn et tDecryptColumn.

La structure du job de cette solution est très semblable à la précédente, mais au lieu d'utiliser un tBase64, nous utilisons ces deux composants.

Afin de varier un peu les plaisirs, les composants de type tBuffer* ont été remplacés par les composants tHashInput/tHashOutput.

Ils ne sont pas disponibles par défaut dans la palette, vous les trouverez via le menu « Fichier/Editer les propriétés du projet », thème technique des paramètres de la palette.

scenario-tcrypt-1png.png

La première partie de ce job est donc la suivante :

scenario-tcrypt-2png.png

Le composant tEncrypt se paramètre comme ci-dessous : vous indiquez simplement que vous souhaitez chiffrer la colonne « value ». 

scenario-tcrypt-3.png

La seconde partie du job chargée le lire le fichier de configuration et de déchiffrer les mots de passe se présente comme suit :

scenario-tcrypt-4.png

Le composant tDecryptColumn se paramètre comme son confrère, avec la même clef, évidemment.

L'inconvénient de cette technique est que la clef utilisée apparaît en clair dans le code Java généré par Talend :

/**
* [tDecryptColumn_1 main ] start
*/
 
currentComponent = "tDecryptColumn_1";
 
// row3
// row3
 
if (execStat) {
runStat.updateStatOnConnection("row3"
+ iterateId, 1, 1);
}
 
javax.crypto.SecretKey Secretkey = new javax.crypto.spec.SecretKeySpec(
"abc4567891011121".getBytes(), "AES");
String theValueAsString = "";

Nous vous proposerons des solutions pour contourner ce problème dans la conclusion du tutoriel.

La sortie du job ressemble à ceci :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |5cA5FSc2fnyviXbZX8Rpqw== |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_Password |secret |
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

On voit clairement que le mot de passe chiffré est aussi encodé en base64, c'est bien.

Ici nous avons réalisé un véritable chiffrement du mot de passe, mais nous avons simplement déporté le problème : la clef permettant de déchiffrer les mots de passe est à son tour fournie en clair dans le code Java de Talend…

A un moment donné, il y a toujours besoin d'avoir la clef en clair… Nous verrons quelles solutions peuvent être proposées pour améliorer cela en fin de ce tutoriel.

Solution 4 : Chiffrement avec JavaXCrypto

Dans cette partie du tutoriel nous utilisons la bibliothèque JavaXCrypto fournie en standard dans le JDK pour chiffrer les données.

scenario-javaxcrypto-1.png

Afin de simplifier le code, du tJavaRow utilisé pour chiffrer/déchiffrer, les fonctions de chiffrement ont été définies dans une routine nommée Encrypt, dont le code se trouve ci-dessous.

Cette classe a été empruntée au site Stackoverflow et un brin simplifiée.

package routines;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
 
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
 
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Base64.Decoder;
/*
* user specification: the function's comment should contain keys as follows: 1. write about the function's comment.but
* it must be before the "{talendTypes}" key.
*
* 2. {talendTypes} 's value must be talend Type, it is required . its value should be one of: String, char | Character,
* long | Long, int | Integer, boolean | Boolean, byte | Byte, Date, double | Double, float | Float, Object, short |
* Short
*
* 3. {Category} define a category for the Function. it is required. its value is user-defined .
*
* 4. {param} 's format is: {param} <type>[(<default value or closed list values>)] <name>[ : <comment>]
*
* <type> 's value should be one of: string, int, list, double, object, boolean, long, char, date. <name>'s value is the
* Function's parameter name. the {param} is optional. so if you the Function without the parameters. the {param} don't
* added. you can have many parameters for the Function.
*
* 5. {example} gives a example for the Function. it is optional.
*/
public class Encrypt {
 
 
private static final char[] PASSWORD = "enfldsgbnlsngdlksdsgm".toCharArray();
private static final byte[] SALT = {
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
(byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,
};
 
 
/**
* encrypt: Return an encrypted String.
*
*
* {talendTypes} String
*
* {Category} User Defined
*
* {param} string("world") input: The string need to be encrypted.
*
* {example} encrypt("Secret") # aebdgsjq01=
*/
public static String encrypt(String property) throws GeneralSecurityException, UnsupportedEncodingException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return base64Encode(pbeCipher.doFinal(property.getBytes("UTF-8")));
}
 
private static String base64Encode(byte[] bytes) {
// NB: This class is internal, and you probably should use another impl
Encoder enc = Base64.getEncoder();
return enc.encodeToString( bytes );
}
 
/**
* decrypt: Return an uncrypted String.
*
*
* {talendTypes} String
*
* {Category} User Defined
*
* {param} string("aebdgsjq01=") input: The string need to be decrypted base64encoded.
*
* {example} decrypt("aebdgsjq01=") # Secret
*/
 
public static String decrypt(String property) throws GeneralSecurityException, IOException {
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20));
return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
}
 
private static byte[] base64Decode(String property) throws IOException {
// NB: This class is internal, and you probably should use another impl
Decoder dec = Base64.getDecoder();
return dec.decode( property ) ;
}
}

Une fois la routine disponible, le composant tJavaRow de chiffrement ressemble à ceci.

//Code généré selon le schémas d'entrée et de sortie
output_row.key = input_row.key;
 
if (input_row.key.toLowerCase().contains("password") ){
output_row.value = Encrypt.encrypt( input_row.value );
} else {
output_row.value = input_row.value;
}

Et celui utilisé pour déchiffrer :

output_row.key = input_row.key;
 
if (input_row.key.toLowerCase().contains("password") ){
output_row.value = Encrypt.decrypt( input_row.value ) ;
} else {
output_row.value = input_row.value;
}

La sortie générée par le job est la suivante :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |QFVQJlYoTC4= |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
 
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Ici la clef de chiffrement est toujours en clair, mais dans la routine, on la retrouve un peu moins facilement. Enfin...

Solution 5 - a : Chiffrement avec la bibliothèque Jasypt

Le projet open source Jasypt permet de réaliser des opérations de chiffrement très facilement en Java. Il propose d'ailleurs une technique intéressante pour lire des paramètres chiffrés dans des fichiers .properties.

Nous l'utiliserons dans un second exemple, celui-ci vise à lire les paramètres de contexte via le composant tContextLoad.

scenario-jasypt-1.png

Ce job est quasiment identique à celui de la première solution.

Il se contente d'ajouter un tLibraryLoad pour charger la bibliothèque Jasypt si elle ne figure pas dans votre CLASSPATH.

scenario-jasypt-2.png

Enfin, le tJavaRow ressemble à ceci :

//Code généré selon le schémas d'entrée et de sortie
output_row.key = input_row.key;
 
if (input_row.key.toLowerCase().contains("password") ){
 
  BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
  textEncryptor.setPassword("0123456789");
 
  output_row.value = textEncryptor.encrypt(input_row.value);
 
} else {
  output_row.value = input_row.value;
}

Et dans la partie « advanced settings » :

import org.jasypt.util.text.BasicTextEncryptor;

Le tJavaRow du décodage contient le code Java suivant :

output_row.key = input_row.key;
 
if (input_row.key.toLowerCase().contains("password") ){
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("0123456789");
 
output_row.value = textEncryptor.decrypt(input_row.value);
} else {
output_row.value = input_row.value;
}

Et le résultat final :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |ry8aAGoaNN+Zhy5umotTqQ== |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
 
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Là encore le mot de passe est stocké en clair, mais Jasypt propose des solutions à cela.

Solution 5 - b : Chiffrement avec la bibliothèque Jasypt

Cette dernière version est un peu plus simple en regard du nombre de composants utilisés.

Les tFileOutputProperties et tJavaRow sont remplacés par un tJavaFlex.

Nous utilisons la classe EncryptableProperties de la bibliothèque Jasypt. Cette classe permet de lire un fichier .properties et de déchiffrer les valeurs des clefs entourées par le texte ENC(<texte chiffré>).

Par contre son code est un peu plus compliqué.

Pour générer le fichier de configuration nous jouons sur le fait que la variable « context » de Talend est du type « java.util.Properties » ce qui nous permet de sauvegarder facilement les paramètres du job.

scenario-jasypt-3.png

Le tJavaFlex de sauvegarde parcourt les paramètres de contexte, et modifie la clef par une valeur chiffrée si c'est un mot de passe.

Puis il sauvegarde ces paramètres dans un fichier.

Son code initial est le suivant :

// start part of your Java code
FileOutputStream fileout = new FileOutputStream("/tmp/config.properties");
BasicTextEncryptor textEncryptor = new BasicTextEncryptor();
textEncryptor.setPassword("0123456789");
Set<String> ek = context.stringPropertyNames();
 
for (String key : ek) {

Son code principal :

// here is the main part of the component,
// a piece of code executed in the row
// loop
String value = "" + context.getProperty(key);
row1.key = key;
if ( key.toLowerCase().contains("password") ){
value = "ENC(" + textEncryptor.encrypt(value) + ")";
}
context.put(key, value);
row1.value = value;

Et son code final :

// end of the component, outside/closing the loop
}

context.save(fileout,
"saving context");
fileout.close();

Les imports sont les suivants :

import java.io.FileOutputStream;
import
org.jasypt.util.text.BasicTextEncryptor;
import
java.util.Set;

Le tJavaFlex de lecture est plus simple et se contente de parcourir toutes les clefs du fichier et de les lire avec la fonction « getProperty » qui déchiffrera automatiquement si la donnée est entourée de « ENC() ».

Son code initial est :

// start part of your Java code
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("0123456789");
 
EncryptableProperties props = new EncryptableProperties(encryptor);
props.load(new FileInputStream("/tmp/config.properties"));
 
for (String key : props.stringPropertyNames()) {

Le principal :

// here is the main part of the component,
// a piece of code executed in the row
// loop
row6.key = key;
row6.value = props.getProperty(key);

Le final :

// end of the component, outside/closing the loop
}

Il est vraiment très simple.

Dans ce dernier tJavaFlex nous aurions pu modifier les paramètres de contexte via la méthode « put » mais cela n'aurait pas positionné les variables accessibles via « context.variable », aussi nous transmettons le flux au tContextLoad qui fait cela très bien.

Et le résultat final ressemble à ceci :

.----------------------+-----------------------------.
| Encoded |
|=---------------------+----------------------------=|
|key |value |
|=---------------------+----------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true |
|MySQL_Login |root |
|MySQL_Port |3306 |
|MySQL_Password |ENC(13T7ETTZly0wCJAGwanllg==)|
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-----------------------------'
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Solution 6 : La solution « Encrypted password store » de la forge

La forge propose un ensemble d'outils nommés « Encrypted password store » et présentée plus en détails sur le site PowerUpBI.

Il s'agit d'une routine à importer dans Talend et d'un jar que vous devez ajouter à votre CLASSPATH. Le développeur de cette solution a commencé à réfléchir sérieusement à cette problématique et propose de sauvegarder tous les mots de passe utilisés par vos jobs dans un « keystore file », autrement dit, un fichier chiffré.

L'idée est évidemment très intéressante, d'autant plus qu'il fournit des scripts permettant de mettre à jour vos mots de passe dans ce fichier depuis la ligne de commande, c'est très pratique.

L'autre grand avantage c'est que votre fichier de configuration ne contient plus le mot de passe, juste le nom de la variable utilisée pour le stocker, le mot de passe est remplacé par des étoiles dans notre exemple. De quoi laisser perplexes les pirates en herbe.

L'inconvénient, est qu'il faut bien fournir quelque part la clef de chiffrement. Et lui non plus n'échappe pas à la règle.

Le job ressemble à ceci :

scenario-pwdstore-1.png

Le composant tLibraryLoad charge la bibliothèque « prowerup-crypt.jar » fournie sur la forge. Sa capture d'écran n'est pas incluse.

Le tJava indique quant-à lui l'emplacement du « keystorefile » et la clef utilisée pour le chiffrer/déchiffrer.

pwdStore.setPwdStore("/tmp/test.pwd", "0123456789");

Le tJavaRow servant à chiffrer les mots de passe se présente comme suit :

output_row.key = input_row.key;
 
if (input_row.key.toLowerCase().contains("password") ){
  pwdStore.setPwd(input_row.key, input_row.value);
  output_row.value = "****";
 
} else {
   output_row.value = input_row.value;
}

Il enregistre le mot de passe dans le « keystore file » puis remplace sa valeur par 4 étoiles, rigolo non ?

Le composant tJavaRow réalisant l'opération inverse contient le code suivant :

//Code généré selon le schémas d'entrée et de sortie

output_row.key = input_row.key;
 

if (input_row.key.toLowerCase().contains("password") ){

output_row.value = pwdStore.getPwd(input_row.key);

} else {

output_row.value = input_row.value;

}

Et le résultat final est le suivant :

.----------------------+-------------------------.
| Encoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |**** |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'
 
.----------------------+-------------------------.
| Decoded |
|=---------------------+------------------------=|
|key |value |
|=---------------------+------------------------=|
|MySQL_AdditionalParams|noDatetimeStringSync=true|
|MySQL_Port |3306 |
|MySQL_Login |root |
|MySQL_Password |secret |
|MySQL_Database |talend |
|MySQL_Server |localhost |
'----------------------+-------------------------'

Cette solution présente une approche plutôt sympathique et un peu plus réfléchie, mais le problème de stockage de la clef reste entier.

Conclusion

Bien que Talend ne propose pas de solution native pour gérer cette problématique, Java et la forge de Talend possèdent des solutions riches et variées.

Toutes ces solutions présentent le même défaut : elles utilisent une clef de chiffrement qui reste en clair dans le code source de Talend. Finalement elles ne font que déporter le problème.

Plusieurs solutions sont proposées dans ces 2 fils de discussion de Stackoverflow :

Voici les solutions que j'ai eu l'occasion de mettre en œuvre ou qui me semblent intéressantes :

  • Ne rien faire de plus mais ne pas exporter le code source de vos jobs, ce sera plus difficile de récupérer un mot de passe dans un fichier .jar ou .class généré par Talend

Evidemment ce n'est pas sécurisé. C'est juste rendre les choses plus difficiles. La véritable question étant de savoir quel est le vrai coût de vraiment sécuriser la solution pour ralentir l'intrusion en regard des risques encourus.

  • Vous pouvez créer une passphrase relativement longue et n'en stocker que la moitié dans le programme Java. L'autre moitié étant passée via la ligne de commande lors de l'exécution de votre job.

C'est moyen, mais un peu mieux que la solution précédente : la clef n'est jamais accessible dans son intégralité.

  • Jasypt propose d'externaliser la lecture des mots de passe afin qu'elles ne figurent pas dans votre code source : en allant les chercher dans une base de données, dans une variable d'environnement supprimée après l'exécution du programme, ailleurs.

  • L'idéal est d'utiliser un « keystore » logiciel/matériel qui vous permet via des commandes système de récupérer le mot passe depuis un label (un peu comme pwdStore) mais en utilisant un plug matériel sur la machine pour chiffrer/déchiffrer ce mot de passe. En dehors de ce matériel la clef de chiffrement n'est jamais accessible.

Par contre n'importe qui pouvant exécuter la commande logicielle pourra déchiffrer vos données en ne passant que le label. Mais généralement ces solutions logicielles vous permettent d'indiquer quels programmes sont autorisés à les appeler. Vous pouvez les configurer pour n'autoriser que vos jobs à déchiffrer.

Ce tutoriel vous a plu ? N'hésitez pas à consulter nos autres tutoriels Talend et notre page de formation Talend.

Pour aller plus loin sur la sécurité, découvrez également notre formation Sécurité Web

ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Makina Corpus devient partenaire Gold de Talend Makina Corpus devient partenaire Gold de Talend 12/05/2009

Talend Tutoriel : comprendre les connexions iterate 20/12/2013

Le lien iterate est décrit assez succinctement dans le manuel utilisateur de Talend (User Guide). ...

Geocoder avec Talend Open Studio 22/05/2014

Mettre en place un job de geocodage d'adresses (depuis un fichier XLS) dans l'ETL Talend Open ...

Créer une carte avec Umap Créer une carte avec Umap 03/07/2019

[ Tuto ] créer une carte utilisant OpenStreetMap et y ajouter ou importer des données, puis ...

Découvrez la formation initiation au Python Scientifique Découvrez la formation initiation au Python Scientifique 24/11/2017

La formation initiation au Python scientifique vous permettra de vous initiez à la programmation ...