英文:
pgx tls connection throws client cert invalid error for valid cert
问题
我正在尝试使用pgx在postgres 10数据库上建立TLS连接。
我的连接字符串类似于:"host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
当我直接在命令行上使用psql
运行时,它可以正常工作,所以证书和密钥文件必须是有效的。db_user.crt
和db_user.key
都是PEM文件。(命令行中的sslmode='verify-full'
也可以正常工作,所以根证书应该也没问题)
但是,当我使用该连接字符串初始化一个pgx
连接池时,它失败并显示以下错误:
> FATAL: connection requires a valid client certificate (SQLSTATE 28000)
Go语言是否需要除PEM之外的其他格式?或者在pgx中初始化ssl证书和密钥对是否有不同的方法?
代码
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
type mockLogger struct{}
func (ml *mockLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
fmt.Printf("[%s] %s : %+v\n", level.String(), msg, data)
}
func connect() error {
connStr := "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
poolCfg, err := pgxpool.ParseConfig(connStr)
if err != nil {
return err
}
poolCfg.ConnConfig.Logger = &mockLogger{}
poolCfg.ConnConfig.LogLevel = pgx.LogLevelTrace
fmt.Printf("using connection string: \"%s\"\n", poolCfg.ConnString())
connPool, err := pgxpool.ConnectConfig(context.TODO(), poolCfg)
if err != nil {
return err
}
connPool.Close()
return nil
}
func main() {
if err := connect(); err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
调用connect()
后的输出:
using connection string: "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='require' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
[info] Dialing PostgreSQL server : map[host:my-host.com]
[error] connect failed : map[err:failed to connect to 'host=my-host.com user=my-db-user database=my-db': server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))]
failed to connect to 'host=my-host.com user=my-db-user database=my-db': server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))
英文:
I'm trying to use pgx to make a TLS connection to a postgres 10 db.
My connection string is similar to: "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
When I run this directly with psql
on the command-line, it works, so the cert and key files must be valid. db_user.crt
and db_user.key
are both PEM files. (the command-line also works with sslmode='verify-full'
, so the rootcert should also be ok)
But when I initialize a pgx
pool with that connection string, it fails with:
> FATAL: connection requires a valid client certificate (SQLSTATE 28000)
Is go expecting something other than PEM? Or is there a different way ssl cert and key pair is supposed to be initialized with pgx?
Code
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
type mockLogger struct{}
func (ml *mockLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
fmt.Printf("[%s] %s : %+v\n", level.String(), msg, data)
}
func connect() error {
connStr := "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='verify-full' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
poolCfg, err := pgxpool.ParseConfig(connStr)
if err != nil {
return err
}
poolCfg.ConnConfig.Logger = &mockLogger{}
poolCfg.ConnConfig.LogLevel = pgx.LogLevelTrace
fmt.Printf("using connection string: \"%s\"\n", poolCfg.ConnString())
connPool, err := pgxpool.ConnectConfig(context.TODO(), poolCfg)
if err != nil {
return err
}
connPool.Close()
return nil
}
func main() {
if err := connect(); err != nil {
fmt.Printf("%+v\n", err)
os.Exit(1)
}
}
Output from calling connect()
:
using connection string: "host='my-host.com' port='5432' dbname='my-db' user='my-db-user' sslmode='require' sslcert='/path/to/db_user.crt' sslkey='/path/to/db_user.key' sslrootcert='/path/to/ca_roots.pem'"
[info] Dialing PostgreSQL server : map[host:my-host.com]
[error] connect failed : map[err:failed to connect to `host=my-host.com user=my-db-user database=my-db`: server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))]
failed to connect to `host=my-host.com user=my-db-user database=my-db`: server error (FATAL: connection requires a valid client certificate (SQLSTATE 28000))
答案1
得分: 0
概要
原来对于Go语言,sslcert
指向的证书需要包含完整的客户端证书链。
当/path/to/db_user.crt
文件包含客户端证书和客户端证书链时,pgx
连接可以正常工作。
而psql
命令在以下两种情况下都可以正常工作:
- 当
sslcert
只包含叶子客户端证书而没有证书链时 - 当
sslcert
包含客户端证书和证书链时
不确定为什么psql
可以在没有完整证书链的情况下正常工作,但现在它可以工作了。
详细信息
在底层,pgx使用pgconn
模块创建连接。而该模块实际上只是在sslcert
和sslkey
文件的内容上调用tls.X509KeyPair
。
func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) {
[...]
sslcert := settings["sslcert"]
sslkey := settings["sslkey"]
[...]
if sslcert != "" && sslkey != "" {
[...]
certfile, err := ioutil.ReadFile(sslcert)
if err != nil {
return nil, fmt.Errorf("unable to read cert: %w", err)
}
cert, err := tls.X509KeyPair(certfile, pemKey)
if err != nil {
return nil, fmt.Errorf("unable to load cert: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
英文:
Summary
Turns out for go, the cert pointed to by sslcert
needed to contain the full client cert chain.
When /path/to/db_user.crt
contained the client cert followed by client cert chain, the pgx
connection worked.
Whereas the psql
command worked in both cases:
- when
sslcert
was just the leaf client cert without the chain - when
sslcert
contained client cert + chain
Not sure why psql was fine without the full chain, but it works now.
Details
Under-the-hood, pgx uses the pgconn
module to create the connection. That, in turn, is just calling tls.X509KeyPair
on the contents of the sslcert
and sslkey
files.
func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) {
[...]
sslcert := settings["sslcert"]
sslkey := settings["sslkey"]
[...]
if sslcert != "" && sslkey != "" {
[...]
certfile, err := ioutil.ReadFile(sslcert)
if err != nil {
return nil, fmt.Errorf("unable to read cert: %w", err)
}
cert, err := tls.X509KeyPair(certfile, pemKey)
if err != nil {
return nil, fmt.Errorf("unable to load cert: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论