C# 如何通过SSL将Google应用程序引擎API(ASP.NET Core 3.1)连接到Google云SQL(Postgres)?
这是我从未问过的最令人沮丧的问题。我如何连接这两者 我有一个API(ASP.NET Core 3.1),我将此应用程序部署到Google的应用程序引擎上。我还有一个连接到的云数据库SQL(PostgreSQL引擎12)。如果没有SSL,连接工作得很好。添加SSL是一个巨大的痛苦。我不断地出现以下错误:C# 如何通过SSL将Google应用程序引擎API(ASP.NET Core 3.1)连接到Google云SQL(Postgres)?,c#,postgresql,google-app-engine,google-cloud-platform,google-cloud-sql,C#,Postgresql,Google App Engine,Google Cloud Platform,Google Cloud Sql,这是我从未问过的最令人沮丧的问题。我如何连接这两者 我有一个API(ASP.NET Core 3.1),我将此应用程序部署到Google的应用程序引擎上。我还有一个连接到的云数据库SQL(PostgreSQL引擎12)。如果没有SSL,连接工作得很好。添加SSL是一个巨大的痛苦。我不断地出现以下错误: Error Connecting to database FATAL : no pg_hba.conf entry for host"x.x.x.x", user"j
Error Connecting to database FATAL : no pg_hba.conf entry for host"x.x.x.x", user"jiradbuser", database"jiradb", SSL off
上面的错误没有任何意义,因为我在GCP上没有任何选项来修改这样的文件
我已经下载了client-cert.pem、client-key.pem和server-ca.pem文件,当我将数据库配置为仅接受SSL连接时,Google给了我这些文件,当我在PSQL中运行以下命令时,我通过SSL进行连接:
psql "sslmode=verify-ca sslrootcert=server-ca.pem sslcert=client-cert.pem sslkey=client-key.pem hostaddr=<cloud sql public ip> port=5432 user=< my username> dbname=<database inside cloud sql instance>"
以下是错误消息:
"exception\":{\"StackTrace\":\" at Interop.Crypto.CheckValidOpenSslHandle(SafeHandle handle)\\n at Internal.Cryptography.Pal.OpenSslX509CertificateReader.FromFile(String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)\\n at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)\\n at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password)\\n at Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)\\n at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)\\n at Npgsql.ConnectorPool.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)\\n at Npgsql.ConnectorPool.<>c__DisplayClass38_0.<<Rent>g__RentAsync|0>d.MoveNext()\\n--- End of stack trace from previous location where exception was thrown ---
“exception\”:{“StackTrace\”:“at Interop.Crypto.CheckValidOpenSslHandle(SafeHandle句柄)\\n at Internal.Cryptography.Pal.OpenSslX509CertificateReader.FromFile(字符串文件名,SafePasswordHandle密码,X509keystrageFlags keystrageFlags)\\n at System.Security.Cryptography.X509Certificates.X509Certificate(字符串文件名,字符串密码,X509KeyStrageFlags KeyStrageFlags)\\n在System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(字符串文件名,字符串密码)\\n在Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout超时,布尔异步,CancellationToken CancellationToken)\\n在Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout,Boolean async,CancellationToken CancellationToken)\\n位于Npgsql.ConnectorPool.OpenNewConnector(NpgsqlConnection conn,NpgsqlTimeout timeout,Boolean async,CancellationToken CancellationToken)\\n位于Npgsql.ConnectorPool.c_udisplayClass38_0.d.MoveNext()\\n---从引发异常的前一个位置开始的堆栈结束跟踪---
是否缺少一个步骤?是否需要将这些文件转换为其他格式(例如.pfx或.crt或.key)?我不认为我必须这样做,因为否则为什么谷歌会首先给我.pem文件?此外,我倾向于将文件本身存储在Google Secret Manager中……这会改变什么吗
我愿意回答您的问题或添加适当的附加信息。谢谢!工作解决方案
在过去的24小时里,我一直在为同一个问题苦恼。我在下面列出了一个有效的解决方案。为第一次尝试表示歉意…那是我在这些论坛上大声思考的,这是不合适的
此解决方案基于一个C#dotnet core SDK 5.0.201控制台应用程序,该应用程序运行在Windows 10 Professional上,带有Google SQL Cloud PostgreSQL 13后端数据库
PEM文件
Google SQL Cloud提供PEM文件;其中三个。两个证书和一个密钥。有一个服务器证书颁发机构证书(Server-ca.PEM)和一个具有相应密钥的客户端证书(Client-cert.PEM和Client-key.PEM)
根据您的示例,psql
命令表明,连接到SQL云需要这三个命令
客户端证书和相应的密钥需要转换为PFX(个人信息交换)格式。此文件格式基本上将客户端证书和密钥配对到单个文件中
有两种方法可以执行此转换…使用openssl
命令或使用C#在代码中执行此转换
你可以在这篇StackOverflow文章的“答案”中找到这两种方法
学分:
如何正确地将证书和密钥传递给Npgsql连接
连接字符串
首先,设置连接字符串。除了SSL证书外,您还需要提供主机、数据库名称、数据库用户名和密码等信息。此外,由于我们针对Google SQL Cloud的SSL连接,您可能已经勾选了“仅允许SSL连接”如果是这样,您还需要将SslMode设置为“Require”(或者根据您的需求/配置设置为“preference”)
客户端证书和密钥
查看客户机证书和密钥,上面的StackOverflow帖子获取客户机证书(Client-cert.pem)和密钥(Client-key.pem),并创建一个组合的PFX证书。如上所述,您可以使用openssl
或在代码中执行此操作。目前,我正在研究“在代码中”在将来发布应用程序时减少任何手动步骤的方法。但是,我已经测试了openssl
方法,并生成了一个PFX文件…它也可以工作
要将PFX证书添加到连接,请使用NpgsqlConnection
类的ProvideClientCertificatesCallback
方法
_pgdb.ProvideClientCertificatesCallback += new ProvideClientCertificatesCallback;
_pgdb.Open();
现在,我们使用自己的实现重载providedclientcertificatescallback
方法,如下所示
public void ProvideClientCertificatesCallback(X509CertificateCollection certificates)
{
var clientCert =
GetCombinedCertificateAndKey(
$@"{certificatePath}\client-cert.pem",
$@"{certificatePath}\client-key.pem");
certificates.Add(clientCert);
Console.WritLine($"client-cert.pem/client-key.perm: {clientCert.Issuer}");
}
方法GetCombinedCertificateAndKey
采用两个参数…客户端证书的文件名和客户端密钥。此方法借用了上面提到的StackOverflow帖子…我稍微修改了一下
private X509Certificate2 GetCombinedCertificateAndKey(string clientCertPath, string clientKeyPath)
{
using var publicKey = new X509Certificate2(clientCertPath); ;
var privateKeyText = System.IO.File.ReadAllText(clientKeyPath);
var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
using var rsa = RSA.Create();
switch (privateKeyBlocks[0]) {
case "BEGIN PRIVATE KEY":
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
break;
case "BEGIN RSA PRIVATE KEY":
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
break;
}
var keyPair = publicKey.CopyWithPrivateKey(rsa);
var Certificate = new X509Certificate2(keyPair.Export(X509ContentType.Pfx));
return Certificate;
}
很高兴通过Visual Studio调试器运行此操作…您可以检查证书的内容。在我的例子中,Google client-key.pem似乎是一个RSA密钥,它是通过switch语句中的此方法获取的。我怀疑所有Google密钥都是相同的
还要注意,return
语句前面的一行……它以组合PFX格式导出客户端证书/密钥对,并将其作为证书返回给调用方法
服务器证书颁发机构证书
对于服务器CA证书,我当前的解决方案是绕过此证书的验证。为此,只需将TrustServerCertificate
属性添加到连接
public void ProvideClientCertificatesCallback(X509CertificateCollection certificates)
{
var clientCert =
GetCombinedCertificateAndKey(
$@"{certificatePath}\client-cert.pem",
$@"{certificatePath}\client-key.pem");
certificates.Add(clientCert);
Console.WritLine($"client-cert.pem/client-key.perm: {clientCert.Issuer}");
}
private X509Certificate2 GetCombinedCertificateAndKey(string clientCertPath, string clientKeyPath)
{
using var publicKey = new X509Certificate2(clientCertPath); ;
var privateKeyText = System.IO.File.ReadAllText(clientKeyPath);
var privateKeyBlocks = privateKeyText.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
using var rsa = RSA.Create();
switch (privateKeyBlocks[0]) {
case "BEGIN PRIVATE KEY":
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
break;
case "BEGIN RSA PRIVATE KEY":
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
break;
}
var keyPair = publicKey.CopyWithPrivateKey(rsa);
var Certificate = new X509Certificate2(keyPair.Export(X509ContentType.Pfx));
return Certificate;
}
var _connectionString = new NpgsqlConnectionStringBuilder()
{
Host = settings.Host,
Database = settings.Database,
Username = settings.Username,
Password = settings.Password,
SslMode = SslMode.Require,
TrustServerCertificate = true,
};
var _pgdb = new NpgsqlConnection(_connectionString.ConnectionString);