pgx tls连接对有效证书抛出客户端证书无效错误。

huangapple go评论121阅读模式
英文:

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.crtdb_user.key都是PEM文件。(命令行中的sslmode='verify-full'也可以正常工作,所以根证书应该也没问题)

但是,当我使用该连接字符串初始化一个pgx连接池时,它失败并显示以下错误:

> FATAL: connection requires a valid client certificate (SQLSTATE 28000)

Go语言是否需要除PEM之外的其他格式?或者在pgx中初始化ssl证书和密钥对是否有不同的方法?

代码

  1. import (
  2. "context"
  3. "fmt"
  4. "os"
  5. "github.com/jackc/pgx/v4"
  6. "github.com/jackc/pgx/v4/pgxpool"
  7. )
  8. type mockLogger struct{}
  9. func (ml *mockLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
  10. fmt.Printf("[%s] %s : %+v\n", level.String(), msg, data)
  11. }
  12. func connect() error {
  13. 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'"
  14. poolCfg, err := pgxpool.ParseConfig(connStr)
  15. if err != nil {
  16. return err
  17. }
  18. poolCfg.ConnConfig.Logger = &mockLogger{}
  19. poolCfg.ConnConfig.LogLevel = pgx.LogLevelTrace
  20. fmt.Printf("using connection string: \"%s\"\n", poolCfg.ConnString())
  21. connPool, err := pgxpool.ConnectConfig(context.TODO(), poolCfg)
  22. if err != nil {
  23. return err
  24. }
  25. connPool.Close()
  26. return nil
  27. }
  28. func main() {
  29. if err := connect(); err != nil {
  30. fmt.Printf("%+v\n", err)
  31. os.Exit(1)
  32. }
  33. }

调用connect()后的输出:

  1. 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'"
  2. [info] Dialing PostgreSQL server : map[host:my-host.com]
  3. [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))]
  4. 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

  1. import (
  2. "context"
  3. "fmt"
  4. "os"
  5. "github.com/jackc/pgx/v4"
  6. "github.com/jackc/pgx/v4/pgxpool"
  7. )
  8. type mockLogger struct{}
  9. func (ml *mockLogger) Log(ctx context.Context, level pgx.LogLevel, msg string, data map[string]interface{}) {
  10. fmt.Printf("[%s] %s : %+v\n", level.String(), msg, data)
  11. }
  12. func connect() error {
  13. 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'"
  14. poolCfg, err := pgxpool.ParseConfig(connStr)
  15. if err != nil {
  16. return err
  17. }
  18. poolCfg.ConnConfig.Logger = &mockLogger{}
  19. poolCfg.ConnConfig.LogLevel = pgx.LogLevelTrace
  20. fmt.Printf("using connection string: \"%s\"\n", poolCfg.ConnString())
  21. connPool, err := pgxpool.ConnectConfig(context.TODO(), poolCfg)
  22. if err != nil {
  23. return err
  24. }
  25. connPool.Close()
  26. return nil
  27. }
  28. func main() {
  29. if err := connect(); err != nil {
  30. fmt.Printf("%+v\n", err)
  31. os.Exit(1)
  32. }
  33. }

Output from calling connect():

  1. 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'"
  2. [info] Dialing PostgreSQL server : map[host:my-host.com]
  3. [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))]
  4. 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模块创建连接。而该模块实际上只是在sslcertsslkey文件的内容上调用tls.X509KeyPair

pgconn/config.go:

  1. func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) {
  2. [...]
  3. sslcert := settings["sslcert"]
  4. sslkey := settings["sslkey"]
  5. [...]
  6. if sslcert != "" && sslkey != "" {
  7. [...]
  8. certfile, err := ioutil.ReadFile(sslcert)
  9. if err != nil {
  10. return nil, fmt.Errorf("unable to read cert: %w", err)
  11. }
  12. cert, err := tls.X509KeyPair(certfile, pemKey)
  13. if err != nil {
  14. return nil, fmt.Errorf("unable to load cert: %w", err)
  15. }
  16. 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.

pgconn/config.go:

  1. func configTLS(settings map[string]string, thisHost string, parseConfigOptions ParseConfigOptions) ([]*tls.Config, error) {
  2. [...]
  3. sslcert := settings["sslcert"]
  4. sslkey := settings["sslkey"]
  5. [...]
  6. if sslcert != "" && sslkey != "" {
  7. [...]
  8. certfile, err := ioutil.ReadFile(sslcert)
  9. if err != nil {
  10. return nil, fmt.Errorf("unable to read cert: %w", err)
  11. }
  12. cert, err := tls.X509KeyPair(certfile, pemKey)
  13. if err != nil {
  14. return nil, fmt.Errorf("unable to load cert: %w", err)
  15. }
  16. tlsConfig.Certificates = []tls.Certificate{cert}

huangapple
  • 本文由 发表于 2022年9月14日 05:33:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/73709371.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定