web-dev-qa-db-ja.com

Tomcatのserver.xmlデータソースのリソース定義のパスワードを平文で保存しないようにするにはどうすればよいですか?

Tomcatのserver.xmlのリソース定義は次のようになります...

<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"/>

パスワードは平文です。これを避ける方法は?

37
dacracot

パスワードを暗号化する前に言ったように、問題を別の場所に移動しているだけです。

とにかく、それは非常に簡単です。秘密鍵などの静的フィールドと、パスワードを暗号化、復号化する静的メソッドを含むクラスを作成するだけです。 Tomcatの構成ファイルでパスワードを暗号化します(server.xmlまたはyourapp.xml...)このクラスを使用します。

Tomcatで「オンザフライ」でパスワードを復号化するには、DBCPのBasicDataSourceFactoryを拡張し、リソースでこのファクトリを使用します。

次のようになります。

    <Resource
        name="jdbc/myDataSource"
        auth="Container"
        type="javax.sql.DataSource"
        username="user"
        password="encryptedpassword"
        driverClassName="driverClass"
        factory="mypackage.MyCustomBasicDataSourceFactory"
        url="jdbc:blabla://..."/>

カスタムファクトリの場合:

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

お役に立てれば。

39
Jerome Delattre

Tomcatはデータベースへの接続方法を知る必要があるため、プレーンテキストパスワードにアクセスする必要があります。パスワードが暗号化されている場合、Tomcatはそれを復号化する方法を知る必要があるため、問題を別の場所に移動するだけです。

実際の問題は、Tomcatを除いて誰がserver.xmlにアクセスできるのかということです。解決策は、server.xmlへの読み取りアクセスをrootユーザーのみに許可することです。Tomcatはroot権限で起動する必要があります。

それ以外の場合は、起動するたびにパスワードを手動で入力する必要がありますが、これが実行可能なオプションになることはほとんどありません。

3
gameame

@Ryanが述べたように、このソリューションを実装する前にTomcatの TomcatパスワードFAQ を読んでください。セキュリティではなく、あいまいさを追加するだけです。

@Jerome Delattreの答えは、単純なJDBCデータソースでは機能しますが、データソース構築の一部として接続するより複雑なデータソース(たとえば、Oracle.jdbc.xa.client.OracleXADataSource)では機能しません。

これは、既存のファクトリを呼び出す前にパスワードを変更する代替アプローチです。以下は、基本的なデータソースとAtomikos JTA互換のXAデータソース用のファクトリーの例です。

基本的な例:

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

アトミコスの例:

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

リファレンスのパスワード値の更新:

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

これらのクラスを含む.jarファイルがTomcatのクラスパスにあると、server.xmlを更新してそれらを使用できます。

<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

注:

WinDPAPIを使用してデータを暗号化および復号化できます

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

4時間の作業の後、質問と回答を検索して解決策を得ました。 @Jerome Delattreの回答に基づいた完全なコードは(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://..."/>

カスタムデータソースファクトリ:

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:

@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