La définition de ressource dans Tomcat's server.xml
ressemble à quelque chose comme ça ...
<Resource
name="jdbc/tox"
scope="Shareable"
type="javax.sql.DataSource"
url="jdbc:Oracle:thin:@yourDBserver.yourCompany.com:1521:yourDBsid"
driverClassName="Oracle.jdbc.pool.OracleDataSource"
username="tox"
password="toxbaby"
maxIdle="3"
maxActive="10"
removeAbandoned="true"
removeAbandonedTimeout="60"
testOnBorrow="true"
validationQuery="select * from dual"
logAbandoned="true"
debug="99"/>
Le mot de passe est en clair. Comment éviter cela?
Comme dit précédemment, le cryptage des mots de passe ne fait que déplacer le problème ailleurs.
Quoi qu'il en soit, c'est assez simple. Écrivez simplement une classe avec des champs statiques pour votre clé secrète et ainsi de suite, et des méthodes statiques pour crypter, décrypter vos mots de passe. Chiffrez votre mot de passe dans le fichier de configuration de Tomcat (server.xml
ou yourapp.xml
...) en utilisant cette classe.
Et pour décrypter le mot de passe "à la volée" dans Tomcat, étendez le BasicDataSourceFactory
du DBCP et utilisez cette fabrique dans votre ressource.
Cela ressemblera à:
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
Et pour l'usine sur mesure:
package mypackage;
....
public class MyCustomBasicDataSourceFactory extends org.Apache.Tomcat.dbcp.dbcp.BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
Object o = super.getObjectInstance(obj, name, nameCtx, environment);
if (o != null) {
BasicDataSource ds = (BasicDataSource) o;
if (ds.getPassword() != null && ds.getPassword().length() > 0) {
String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
ds.setPassword(pwd);
}
return ds;
} else {
return null;
}
}
J'espère que cela t'aides.
Tomcat a ne FAQ sur les mots de passe qui répond spécifiquement à votre question. En bref: conservez le mot de passe en clair et verrouillez correctement votre serveur.
Cette page propose également quelques suggestions sur la façon dont la sécurité par obscurité pourrait être utilisée pour passer la liste de contrôle d'un auditeur.
Tomcat doit savoir comment se connecter à la base de données, il doit donc accéder au mot de passe en texte brut. Si le mot de passe est crypté, Tomcat doit savoir comment le décrypter, vous ne déplacez donc le problème que ailleurs.
Le vrai problème est: qui peut accéder à server.xml
sauf Tomcat? Une solution consiste à donner un accès en lecture à server.xml
uniquement pour l'utilisateur root, ce qui nécessite que Tomcat soit démarré avec des privilèges root: si un utilisateur malveillant obtient des privilèges root sur le système, la perte d'un mot de passe de base de données est probablement une préoccupation mineure.
Sinon, vous devez taper le mot de passe manuellement à chaque démarrage, mais c'est rarement une option viable.
Comme @Ryan l'a mentionné, veuillez lire Tomcat Tomcat Password FAQ avant de mettre en œuvre cette solution. Vous ajoutez seulement de l'obscurité et non de la sécurité.
La réponse de @Jerome Delattre fonctionnera pour les sources de données JDBC simples, mais pas pour les sources plus compliquées qui se connectent dans le cadre de la construction de la source de données (par exemple Oracle.jdbc.xa.client.OracleXADataSource).
Il s'agit d'une approche alternative qui modifie le mot de passe avant d'appeler l'usine existante. Voici un exemple d'une usine pour une source de données de base et une pour une source de données XA compatible Atomikos JTA.
Exemple de base:
public class MyEncryptedPasswordFactory extends BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws Exception {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
Exemple Atomikos:
public class MyEncryptedAtomikosPasswordFactory extends EnhancedTomcatAtomikosBeanFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws NamingException {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "xaProperties.password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
Mise à jour de la valeur du mot de passe dans la référence:
public class DecryptPasswordUtil {
public static void replacePasswordWithDecrypted(Reference reference, String passwordKey) {
if(reference == null) {
throw new IllegalArgumentException("Reference object must not be null");
}
// Search for password addr and replace with decrypted
for (int i = 0; i < reference.size(); i++) {
RefAddr addr = reference.get(i);
if (passwordKey.equals(addr.getType())) {
if (addr.getContent() == null) {
throw new IllegalArgumentException("Password must not be null for key " + passwordKey);
}
String decrypted = yourDecryptionMethod(addr.getContent().toString());
reference.remove(i);
reference.add(i, new StringRefAddr(passwordKey, decrypted));
break;
}
}
}
}
Une fois que le fichier .jar contenant ces classes se trouve dans le chemin d'accès aux classes de Tomcat, vous pouvez mettre à jour votre server.xml pour les utiliser.
<Resource factory="com.mycompany.MyEncryptedPasswordFactory" username="user" password="encryptedPassword" ...other options... />
<Resource factory="com.mycompany.MyEncryptedAtomikosPasswordFactory" type="com.atomikos.jdbc.AtomikosDataSourceBean" xaProperties.user="user" xaProperties.password="encryptedPassword" ...other options... />
Remarque:
Vous pouvez utiliser WinDPAPI pour crypter et décrypter les données
public class MyDataSourceFactory extends DataSourceFactory{
private static WinDPAPI winDPAPI;
protected static final String DATA_SOURCE_FACTORY_PROP_PASSWORD = "password";
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception{
Reference ref = (Reference) obj;
for (int i = 0; i < ref.size(); i++) {
RefAddr ra = ref.get(i);
if (ra.getType().equals(DATA_SOURCE_FACTORY_PROP_PASSWORD)) {
if (ra.getContent() != null && ra.getContent().toString().length() > 0) {
String pwd = getUnprotectedData(ra.getContent().toString());
ref.remove(i);
ref.add(i, new StringRefAddr(DATA_SOURCE_FACTORY_PROP_PASSWORD, pwd));
}
break;
}
}
return super.getObjectInstance(obj, name, nameCtx, environment);
}
}
Après 4 heures de travail, recherche de questions et réponses, j'ai trouvé la solution. Basé sur la réponse de @Jerome Delattre, voici le code complet (avec la configuration de la source de données JNDI).
Context.xml
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
Usine de source de données personnalisée:
package mypackage;
public class MyCustomBasicDataSourceFactory extends org.Apache.Tomcat.dbcp.dbcp.BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
Object o = super.getObjectInstance(obj, name, nameCtx, environment);
if (o != null) {
BasicDataSource ds = (BasicDataSource) o;
if (ds.getPassword() != null && ds.getPassword().length() > 0) {
String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
ds.setPassword(pwd);
}
return ds;
} else {
return null;
}
}
}
Bean source de données:
@Bean
public DataSource dataSource() {
DataSource ds = null;
JndiTemplate jndi = new JndiTemplate();
try {
ds = jndi.lookup("Java:comp/env/jdbc/myDataSource", DataSource.class);
} catch (NamingException e) {
log.error("NamingException for Java:comp/env/jdbc/myDataSource", e);
}
return ds;
}