具有不同TLS证书的多端点Kestrel配置提供了错误的证书

时间:2019-08-17 11:10:11

标签: c# ssl asp.net-core kestrel-http-server

我正在尝试通过其appsettings.json配置文件在Linux环境中的基于ASP.NET Core MVC的服务应用程序中设置Kestrel服务器,以便它具有两个端点,两个端点都使用HTTPS,但是每个端点其他证书。一个端点监听0.0.0.0,另一个端点监听localhost。该服务充当IdentityServer4的主机。服务器的预期运行时环境将是Docker容器,该容器是基于多服务的系统的一部分,当前作为docker-compose集群运行。

我已经研究了https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.2#endpoint-configuration上的官方文档,并认为应该容易实现。我创建了一个自签名的根CA证书,并生成了两个由根签名的证书。其中之一将“公用名”设置为容器在群集中将具有的名称,并用于与外部环境进行安全通信。另一个证书的CN设置为localhost,因此使用该证书是因为服务器具有多个控制器,这些控制器可以从外部使用,并且它们都将转换传入的请求并将其转发到将处理这些请求的特殊内部控制器。 (我意识到我有点偏执,但这不重要。)

我尝试配置Kestrel,以使主要端点使用默认证书,而localhost端点使用临时证书,即:

  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://+:2051"
      },
      "HttpsLocalhost": {
        "Url": "https://localhost:2052",
        "Certificate:": {
          "Path": "path/to/localhost-dev.pfx",
          "Password": "password"
        }
      }
    },
    "Certificates": {
      "Default": {
        "Path": "path/to/identity-api-dev.pfx",
        "Password": "password"
      }
    }
  }

这似乎是文档中正确的配置,它说如果显式配置HTTPS,则服务器需要定义默认证书。

面向外部的控制器使用延迟初始化的HttpClient,其BaseAddress属性在收到的第一个请求中使用localhost端点的URL初始化。该网址是使用此方法轻松确定的

private string GetIdentityServiceHostAndPort()
{
    IServerAddressesFeature addresses = Server.Features.Get<IServerAddressesFeature>();

    // Get the first that uses HTTPS and is on localhost
    foreach (string url in addresses.Addresses)
    {
        if (url.StartsWith("https://localhost"))
        {
            return url;
        }
    }

    throw new ArgumentException("Could not determine identity service's host/port, is it listening on localhost using HTTPS?");
}

上述方法返回的地址然后传递到以下方法,该方法使用该方法初始化控制器的HttpClient实例,并向IS4的OpenID配置端点查询发现文档,并使用该端点进一步初始化控制器:

private static async Task InitializeTokenEndpointHttpClientAsync(string baseAddress)
{
    TokenEndpointClient = new HttpClient();
    TokenEndpointClient.BaseAddress = new Uri(baseAddress);

    HttpResponseMessage response = await TokenEndpointClient.GetAsync(".well-known/openid-configuration");
    string content = await response.Content.ReadAsStringAsync();
    if (response.StatusCode != HttpStatusCode.OK)
    {
        throw new InvalidOperationException("Error querying IdentityServer for discovery document: " + content);
    }

    DiscoveryDocument document = JsonConvert.DeserializeObject<DiscoveryDocument>(content);
    Uri fullTokenEndpointUri = new Uri(document.token_endpoint);
    TokenEndpointPath = fullTokenEndpointUri.LocalPath;
}

由于我已经花费了大量时间尝试调试此问题,因此我确保已将服务器配置为在具有一个端口的任何地址上以及在另一端口localhost上监听。 GetIdentityServiceHostAndPort()方法确实返回正确的URL。

现在,我面临的问题是上述方法中对发现文档的请求死于

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.

很明显,证书验证过程失败了,这对我来说似乎很可疑,因为应该将请求转到设置了CN=localhost的证书的端点。

当我尝试使用手动请求发现文件时

curl -g 'https://localhost:2052/.well-known/openid-configuration'

我收到此消息

curl: (60) SSL: certificate subject name 'identity.api' does not match target host name 'localhost'

我不明白服务器为什么会尝试发送此证书。我知道它是已配置的默认值,但是也为localhost端点明确定义了证书,我希望在这种情况下可以使用该证书。

我确定我会丢失一些东西,但是正如我所说,我已经花了几个小时试图找到罪魁祸首,所以我将不胜感激。

1 个答案:

答案 0 :(得分:3)

仅是为了在所有人面前同时清楚地使自己感到尴尬,但问题是我一直在Kestrel配置部分中忽略错字。 Certificate小节的HttpsLocalhost键字符串中有一个多余的冒号,如果我的生活依赖它,我将无法发现。

很遗憾,它没有,而且配置在此修复程序之后可以正常工作。