如何通过SSL连接PostgreSQL数据库?

17

我已经创建了自己的证书并配置了postgresql.conf文件:

...
#authentication_timeout = 1min          # 1s-600s
ssl = true                              # (change requires restart)
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL' # allowed SSL ciphers
                                        # (change requires restart)
#ssl_prefer_server_ciphers = on         # (change requires restart)
#ssl_ecdh_curve = 'prime256v1'          # (change requires restart)
ssl_cert_file = '/etc/ssl/certs/company/database/certificate'           # (change requires restart)
ssl_key_file = '/etc/ssl/certs/company/database/key'            # (change requires restart)
ssl_ca_file = '/usr/share/ca-certificates/company/ca/certificate'                        # (change requires restart)
#ssl_crl_file = ''                      # (change requires restart)
#password_encryption = on
#db_user_namespace = off
#row_security = on
...

然后,我允许我的服务器与我的数据库 pg_hba.conf 进行连接:

...
hostssl    postgres             postgres             XXX.XXX.XXX.XXX/0            md5
...

所以,我可以通过psql命令行连接到它:

psql (9.5.3)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.

postgres=#

但是,当我尝试通过我的Java应用程序与数据库建立连接时,即使我提供了包含数据库证书的信任存储,我仍然无法与其建立连接:

mvn package -Djavax.net.ssl.trustStore=/opt/app/truststore -Djavax.net.ssl.trustStorePassword=changeit

异常:

2016-05-23 16:28:32,900 WARN [com.mchange.v2.resourcepool.BasicResourcePool] - <Having failed to acquire a resource, com.mchange.v2.resourcepool.BasicResourcePool@75fa1be3 is interrupting all Threads waiting on a resource to check out. Will try again in response to new client requests.>
2016-05-23 16:28:32,900 WARN [com.mchange.v2.resourcepool.BasicResourcePool] - <com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask@2be057bf -- Acquisition Attempt Failed!!! Clearing pending acquires. While trying to acquire a needed new resource, we failed to succeed more than the maximum number of allowed acquisition attempts (30). Last acquisition attempt exception: >
java.lang.NullPointerException
    at org.postgresql.Driver.parseURL(Driver.java:532)
    at org.postgresql.Driver.acceptsURL(Driver.java:431)
    at java.sql.DriverManager.getDriver(DriverManager.java:299)
    at com.mchange.v2.c3p0.DriverManagerDataSource.driver(DriverManagerDataSource.java:285)
    at com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:161)
    at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:161)
    at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:147)
    at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.java:202)
    at com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.java:1138)
    at com.mchange.v2.resourcepool.BasicResourcePool.doAcquireAndDecrementPendingAcquiresWithinLockOnSuccess(BasicResourcePool.java:1125)
    at com.mchange.v2.resourcepool.BasicResourcePool.access$700(BasicResourcePool.java:44)
    at com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask.run(BasicResourcePool.java:1870)
    at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:696)
2016-05-23 16:28:32,901 WARN [com.mchange.v2.resourcepool.BasicResourcePool] - <Having failed to acquire a resource, com.mchange.v2.resourcepool.BasicResourcePool@75fa1be3 is interrupting all Threads waiting on a resource to check out. Will try again in response to new client requests.>

通过 psql 一切似乎都正常工作,但我的应用程序不行。有什么建议吗?

编辑:

我的 props.properties 文件:

uatDb.user=postgres
uatDb.password=password
uatDb.driverClass=org.postgresql.Driver
uatDb.jdbcUrl=jdbc:postgresql://<server_name>:1234/uat?ssl=true
uatDb.port=5443
uatDb.name=uat
uatDb.host=<server_name>

1
发布你的Java代码。你是否为SSL设置了连接属性?请参阅https://jdbc.postgresql.org/documentation/head/connect.html - Andrew Henle
@AndrewHenle 是的,我已经在一个文件中设置了这个连接的属性。我已经编辑了我的问题。我通过maven调用我的数据库来构建我的应用程序,以便访问我的数据库进行一些测试。 - Valter Silva
1
假设您的连接没有启用SSL,您可以在MAVEN_OPTS中设置“-Djavax.net.debug=all”以获取Java连接调试数据吗?请参见https://dev59.com/9HI-5IYBdhLWcg3wHUjP(我不是Maven大师...) - Andrew Henle
6个回答

7
url=jdbc:postgresql://<host_url_or_ip>:<port>/<db_name>?currentSchema=<schema_name>&sslmode=verify-ca&sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory

注意:如果 schema_namepublic,则不需要提供该参数。但是,即使端口使用默认值(例如5432),您仍然必须提供它。
对于 sslmode 值的参考,请查看以下链接:https://jdbc.postgresql.org/documentation/head/ssl-client.html。要启用验证,请设置 sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory
对于非验证 SSL 连接,可以使用 sslfactory=org.postgresql.ssl.NonValidatingFactory
但请记住,一旦启用 SSL 验证,可能需要根 CA 证书。
以下是可选方法(可能不尽全面,但这些方法已经为我工作):
  1. 将其放置在默认位置即 ~/Postgres/root.crt OR
  2. PGSSLROOTCERT环境变量设置为其路径 OR
  3. 导入到信任存储中,并将其路径传递给: -Djavax.net.ssl.trustStore=[trust_store_path] -Djavax.net.ssl.trustStorePassword=[trust_store_password]。 如果您使用默认信任存储即 JRE 的 cacerts,则不需要这两个环境变量。
参考链接: https://jdbc.postgresql.org/documentation/head/ssl-client.html https://www.postgresql.org/docs/9.0/libpq-ssl.html

4
如果您不需要验证数据库客户端,只需在postgresql.conf中注释掉ssl_ca_file即可。

ssl_ca_file - 可信证书颁发机构,检查客户端证书是否由受信任的证书颁发机构签名。

您的数据库URL应该像这样:
url=jdbc:postgresql://host:5432/db_name?ssl=true&sslmode=require

请参考PostgreSQL JDBC文档获取有关sslmode的更多详细信息。
否则,如果您需要验证数据库客户端,则应将sslcert=...sslkey=...参数添加到您的数据库URL中。

你好!如何使用sslkey=?我是否还需要sslcert=或仅使用密钥就可以工作? - Ashutosh Tiwari
@AshutoshTiwari,它将类似于这样:jdbc:postgresql://host:5432/db_name?sslmode=verify-full&sslrootcert=CA.crt&sslcert=db-client.crt&sslkey=db-client.pk8 - Oleksandr Shmyrko

3

我认为您还需要一个密钥库。因为ssl=true应该要求您的自签名证书有私钥。我还会添加“nonValidating SSLFactory”设置。

?ssl=true&sslfactory=org.postgresql.ssl.NonValidatingFactory

此外,server_name:1234 的端口设置与 5432 不匹配。

"parseUrl" 是一个空错误,可能是 JDBC URL 中的某个变量设置不正确。


3
“org.postgresql.ssl.NonValidatingFactory” 工厂会绕过所有 SSL 证书验证,使 SSL 连接毫无意义。 - Graham Leggett
1
不确定 &amp; 是否正确或必要。对我来说,只使用 & 就可以工作。 - hgoebl

2

对于使用Springboot和SSL的用户

将密钥从pem格式转换为pkcs8格式

openssl pkcs8 -topk8 -inform PEM -outform DER \
   -in client-key.pem \
   -out client-key.pkcs8 -nocrypt

在 applications.properties 文件中添加证书到数据库 URL。

spring.datasource.url=jdbc:postgresql://<DB_IP>/mydbName?ssl=true&sslmode=verify-ca\
&sslcert=BOOT-INF/classes/db/certs/client-cert.pem\
&sslkey=BOOT-INF/classes/db/certs/client-key.pkcs8\
&sslrootcert=BOOT-INF/classes/db/certs/server-ca.pem

嘿,如果有人找到一种将这些证书/密钥放入环境变量并在jdbc-url中使用的方法,请编辑/添加该信息。 这将更好地与云原生/k8s对齐。


1
我在使用JFrog Artifactory连接到Azure Postgres时遇到了问题,以下步骤对我有效:
sudo cat <<EOF >/opt/jfrog/artifactory/var/etc/system.yaml
configVersion: 1
shared:
    node:
    database:
        type: postgresql
        driver: org.postgresql.Driver
        url: "jdbc:postgresql://test-psqlserver.postgres.database.azure.com:5432/postgres?ssl=true&sslmode=require"
        username: psqladminun@test-psqlserver
        password: password:-)
EOF

0

https://stackoverflow.com/users/3612771/d-v-santhosh-kiran 这位朋友说的有点道理。

其中一些信息并不完全准确。 sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory 是好的,但它依赖于 SSLSocketFactory.getDefault(),该方法知道如何从 $JAVA_HOME/lib/security/cacerts 或 javax.net.ssl.* 属性中读取 CA 信任库,具体见该答案的第三点。如果您使用默认的 sslfactory org.postgresql.ssl.LibPQFactory,则点 1 和 2 是正确的,但与 DefaultJavaSSLFactory 完全不兼容。所以要小心。

此外,我提议选项 4。将根CA添加到 JRE cacerts 文件中,让 Java 执行其默认操作。 你需要用 keytool 进行此操作,但如果在运行时有多个客户端,则比 java.net.ssl 设置更灵活。

keytool -importcert -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt -alias "$ALIAS" -file $YOUR_CERT -trustcacerts

请参考https://docs.oracle.com/en/java/javase/11/tools/keytool.html,具体情况可能有所不同。您要做的是将(x509/pkix)证书导入cacerts Java密钥库。

fwiw org.postgresql.ssl.LibPQFactory是一个糟糕的名称,会让人们认为postgres驱动程序会执行一些JNI并调用C。这不是作者的本意。该类可以被命名为SslFactoryThatDoesPathsLikeLibPQBro。它只是像LibPQ一样查找路径中的证书。除此之外,它使用常规的Java JSSE来加载TLS的信任库证书。 请查看源代码:https://github.com/pgjdbc/pgjdbc/blob/master/pgjdbc/src/main/java/org/postgresql/ssl/LibPQFactory.java#L102

SSLContext ctx = SSLContext.getInstance("TLS");
... stuff about libpq path locations ...
TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
...
ks = KeyStore.getInstance("jks");
...
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{});
ks.load(null, null);
for (int i = 0; i < certs.length; i++) {
  ks.setCertificateEntry("cert" + i, (Certificate) certs[i]);
}
tmf.init(ks);
tm = tmf.getTrustManagers();
...
// finally we can initialize the context
...
        KeyManager km = this.km;
        ctx.init(km == null ? null : new KeyManager[]{km}, tm, null);
...
      factory = ctx.getSocketFactory();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接