web-dev-qa-db-fra.com

Comment éviter de stocker des mots de passe en clair pour la définition de ressource server.xml de Tomcat d'une source de données?

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?

37
dacracot

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.

39
Jerome Delattre

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.

10
Ryan

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.

3
gameame

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... />
3
JustinKSU

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);
  }
}
2

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;
}
2
HolloW