【问题标题】:Deploying .net Core 3 linux container on azure web app container with IdentityServer4 certification/http error在带有 IdentityServer4 认证/http 错误的 azure Web 应用程序容器上部署 .net Core 3 linux 容器
【发布时间】:2021-09-01 02:56:43
【问题描述】:

我正在尝试使用 .Net Core Clean Architecture App Template 并让它在容器中运行并通过 azure CI/CD 管道进行部署

我有模板的容器化版本在 linux 容器中本地运行,端口为 5001,一切正常。

我的 azure 管道构建过程正常工作,它在我的容器注册表中创建映像。

问题是一旦我部署/发布到容器的 Web 应用程序,应用程序失败并抛出以下错误:

应用程序启动异常 System.InvalidOperationException:在 'CurrentUser\My' 上找不到主题为 'CN=localhost' 的有效证书 Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromStoreCert(字符串 主题, String storeName, StoreLocation storeLocation, DateTimeOffset currentTime)

我做了什么:

  1. these docs from MS 之后,我创建了一个本地开发证书:

    dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p { password here }

    dotnet dev-certs https --trust

  2. 然后我将其作为私有 .pfx 证书导入到 Web 应用程序中。

  3. 我添加了一个应用程序设置 WEBSITE_LOAD_CERTIFICATES 与证书的“thumb”值

  4. 我在 Identity Server appSettings.json 部分中使用了导入证书的“主机名”(在我的例子中是主机名=localhost)

当 Web 应用程序加载时,它显示 :( 应用程序错误和 docker 日志给了我上面引用的错误。

我很确定这与身份服务器设置和此处的 appSettings.json 值有关:

  "IdentityServer": {
    "Key": {
      "Type": "Store",
      "StoreName": "My",
      "StoreLocation": "CurrentUser",
      "Name": "CN=localhost"
    }
  }

谁能帮我弄清楚如何解决这个错误?

编辑 1 - 手动指定 IdentityServer 密钥文件

这肯定与身份服务器有关。我尝试手动将证书设置为 appSettings.json 中的文件,如下所示:

  "IdentityServer": {
    "Key": {
      "Type": "File",
      "FilePath": "aspnetapp.pfx",
      "Password": "Your_password123"
    }
  }

现在我收到此错误:

使用存储标志在“/app/aspnetapp.pfx”加载证书文件 ''。应用程序启动异常 System.InvalidOperationException: 加载证书时出错。文件 '/app/aspnetapp.pfx' 未找到。 Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromFile

我将此添加到 dockerfile 中:

WORKDIR /app
COPY ["/aspnetapp.pfx", "/app"]
RUN find /app

如下图所示,文件显示在应用的构建目录中:

我还确保 .gitignore 或 .dockerignore 文件不会忽略 aspnetapp.pfx。

我不知道为什么它不会加载这个文件。它似乎就在它应该存在的地方。

使用证书拇指和更新路径编辑 2

所以我使用了 tnc1977 建议并将其作为身份密钥的设置

  "IdentityServer": {
    "Key": {
      "Type": "File",
      "FilePath": "/var/ssl/private/<thumb_value>.p12",
      "Password": "Your_password123"
    }
  }

但是,这又出现了另一个错误:

加载证书时出错。要么是密码 不正确或进程没有权限将密钥存储在 键集“EphemeralKeySet” 互操作+加密+OpenSslCryptographicException:错误:23076071:PKCS12 routines:PKCS12_parse:mac 验证失败

编辑 3:有效的 Azure 应用证书

我购买了 Azure 应用证书并添加了一个设置了 TSL 的自定义域,但出现了相同的错误

编辑 4:在代码 startup.cs 中加载证书 - 新错误:

我现在知道我不能使用证书存储 CurrentUser/My,因为它适用于 Windows。 Linux 容器必须在代码中手动加载证书。

我正在使用已添加到 Azure Web 应用程序的应用程序证书的指纹。它是一个私有 azure 应用程序证书,并且已针对自定义域进行了验证。

我将此代码添加到我的 statup.cs 配置服务中(我知道硬编码这些值不是最佳做法,但我想看看它是否可以加载证书,我将查看环境变量和密钥库):

        // linux file path for private keys
        var cryptBytes = File.ReadAllBytes("/var/ssl/private/<thumbprint>.p12");
        var cert = new X509Certificate2(cryptBytes, "");

        services.AddIdentityServer().AddSigningCredential(cert);

我输入了一个空白密码,因为我认为这是您应该做的。我现在在我的 docker 日志中收到以下错误,这使我相信已加载证书,现在该错误与我使用 services.AddIdentityServer().AddSigningCredential(cert); 相关 在 startup.cs configureservicesapp.UseIdentityServer() 在 startup.cs configure

未处理的异常。 System.InvalidOperationException:装饰器已注册类型:IAuthenticationService。

我不确定如何将证书添加到 app.UseIdentityServer();行。

编辑 5

经过更多的挖掘,不幸的是@tnc1997 答案将不起作用。 在 asp.net core 3 中调用 app.UseIdentityServer 在我的 satrtup.cs 内部崇敬一种将寻找身份服务器密钥的方法,在 appsetting(environment).json 文件中的 File,Pass 等。

因此,即使我在 tnc1997 显示的代码中加载了证书,应用程序仍会在设置文件中查找。因此设置文件必须包含 IS4 密钥的正确详细信息。

此外,azure 不会将证书放置在 linux 容器中典型的受信任位置。根据我的阅读,似乎唯一的方法是安装一个卷(在这种情况下是一个 azure 存储文件共享)并使用上传到该文件共享的证书。

我可以确认这在本地工作,但现在我在运行容器时仍然遇到问题,前端加载并且似乎 web api 项目没有启动。我将发布另一个问题来解决这个问题。

【问题讨论】:

  • 嗨,J King,您提到您在本地使用的是 Linux 容器,能否确认您在 Azure 中使用的是 Windows 容器?
  • @tnc1997 你好,azure中的容器是linux容器
  • 您有没有找到实际的解决方案?使用密码和自签名证书在 appsettings.json 中一切正常……直到我升级了 Identity Server……
  • @ZaneClaes 还没有。仍在努力中
  • 我也陷入了同样的陷阱。身份服务器是一群不友好的人,他们会推动你寻求商业支持。我想编写自己的 oauth 身份验证并忘记这个传奇......它的总 b/s

标签: azure docker azure-devops azure-pipelines identityserver4


【解决方案1】:

原答案

我认为问题可能是您尝试使用 Windows 证书存储在 Linux 容器中加载证书。

here 文档很好地概述了如何在 Linux 托管应用中使用应用服务私有证书:

  1. 在 Azure 门户的左侧菜单中,选择应用服务 >
  2. 从应用的左侧导航中,选择 TLS/SSL 设置,然后选择私钥证书 (.pfx) 或公钥证书 (.cer)。
  3. 找到您要使用的证书并复制指纹。
  4. 要访问应用代码中的证书,请将其指纹添加到 WEBSITE_LOAD_CERTIFICATES 应用设置。
  5. WEBSITE_LOAD_CERTIFICATES 应用程序设置使您的 Linux 托管应用程序(包括自定义容器应用程序)可以作为文件访问指定的证书。这些文件位于以下目录下:
    • 私有证书 - /var/ssl/private(.p12 文件)
    • 公共证书 - /var/ssl/certs(.der 文件)
  6. 使用下面的代码示例将指定的证书加载到您的 Linux 托管应用程序(包括自定义容器应用程序)中:
    using System;
    using System.IO;
    using System.Security.Cryptography.X509Certificates;
    
    var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
    var cert = new X509Certificate2(bytes);
    

签署凭证

以下是我用来生成签名凭据的步骤:

  1. 安装OpenSSL
  2. 生成私钥和公共证书。
    1. 运行 openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout example.com.key -out example.com.crt -subj "/CN=example.com" -days 365,将 example.com 替换为站点名称。
  3. 将以上内容组合成一个 PFX 文件。
    1. 运行 openssl pkcs12 -export -out example.com.pfx -inkey example.com.key -in example.com.crt,将 example.com 替换为站点名称。
  4. 将 PFX 文件上传到 Azure。
    1. 在 Azure 门户的左侧菜单中,选择应用服务 >
    2. 从应用的左侧导航中,选择 TLS/SSL 设置,然后选择私钥证书 (.pfx),然后上传上述 PFX 文件。
  5. 配置应用程序设置。
    1. 将上述 PFX 文件的指纹添加到应用服务中的 WEBSITE_LOAD_CERTIFICATES 应用设置。

身份服务器

以下代码示例显示了一个完整的Startup.cs 配置,可用于启动和运行 IdentityServer 应用程序:

namespace IdentityServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration, IWebHostEnvironment environment)
        {
            Configuration = configuration;
            Environment = environment;
        }

        public IConfiguration Configuration { get; }

        public IWebHostEnvironment Environment { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            void ConfigureDbContext(DbContextOptionsBuilder builder)
            {
                builder.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
            }

            var builder = services.AddIdentityServer()
                .AddConfigurationStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
                .AddOperationalStore(options => { options.ConfigureDbContext = ConfigureDbContext; });

            if (Environment.IsDevelopment())
            {
                builder.AddDeveloperSigningCredential();
            }
            else
            {
                try
                {
                    var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
                    var certificate = new X509Certificate2(bytes);
                    builder.AddSigningCredential(certificate);
                }
                catch (FileNotFoundException)
                {
                    throw new Exception($"The certificate with the thumbprint \"{Configuration["WEBSITE_LOAD_CERTIFICATES"].Substring(0, 8)}...\" could not be found.");
                }
            }
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

            app.UseIdentityServer();
        }
    }
}

简洁的架构

下面的代码示例显示了一个完整的 DependencyInjection.cs 配置,可用于启动和运行 Clean Architecture 应用程序:

namespace CleanArchitecture.Infrastructure
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            void ConfigureDbContext(DbContextOptionsBuilder builder)
            {
                if (configuration.GetValue<bool>("UseInMemoryDatabase"))
                {
                    builder.UseInMemoryDatabase("CleanArchitectureDb");
                }
                else
                {
                    builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
                }
            }

            services.AddDbContext<ApplicationDbContext>(ConfigureDbContext);

            services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());

            services.AddScoped<IDomainEventService, DomainEventService>();

            services.AddDefaultIdentity<ApplicationUser>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            var builder = services.AddIdentityServer()
                .AddConfigurationStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
                .AddOperationalStore(options => { options.ConfigureDbContext = ConfigureDbContext; })
                .AddAspNetIdentity<ApplicationUser>();

            var bytes = File.ReadAllBytes($"/var/ssl/private/{Configuration["WEBSITE_LOAD_CERTIFICATES"]}.p12");
            var certificate = new X509Certificate2(bytes);
            builder.AddSigningCredential(certificate);

            services.AddTransient<IDateTime, DateTimeService>();
            services.AddTransient<IIdentityService, IdentityService>();
            services.AddTransient<ICsvFileBuilder, CsvFileBuilder>();

            services.AddAuthentication()
                .AddIdentityServerJwt();

            return services;
        }
    }
}

【讨论】:

  • 感谢您的跟进!我使用了配置文件版本,现在我收到以下错误:加载证书时出错。密码不正确或进程没有权限将密钥存储在 Keyset 'EphemeralKeySet' Interop+Crypto+OpenSslCryptographicException: error:23076071:PKCS12 routines:PKCS12_parse:mac verify failure
  • 但是我使用开放式 SSL 来验证密码,如果密码错误,它不会让您上传到门户网站。所以不能是密码。
  • 问题可能是我使用了像@Tore 建议的本地生成的证书。
  • 我能够在我的生产 IdentityServer 中使用自签名证书,该证书部署在 Azure 的 Linux 容器中,但是我直接配置了 IdentityServer,而不是使用 Microsoft.AspNetCore.ApiAuthorization.IdentityServer 包。您是否有可能尝试使用第 6 步中的第一个代码示例而不是第二个代码示例来加载证书?
  • 是的,看看那个 GitHub 链接,我可以看到它们有很大的不同,因此我添加了一个额外的代码示例,它显示了您可能能够配置 Clean Architecture 应用程序的一种方式使用您的签名凭据。
【解决方案2】:

我认为问题在于容器中的应用程序不信任本地创建的开发人员证书。它只能在您的计算机上使用,因为您的计算机上安装了开发根证书。

容器永远不会信任由 dotnet dev-certs 创建的证书。

您需要获得正确信任的证书,例如来自LetsEncrypt

【讨论】:

    【解决方案3】:

    我在 linux 应用服务上运行 .net core spa 模板时遇到了这个问题。我还创建了一个自签名的 .pfx,如 tnc1997 的回答中所述。虽然答案可以拼凑起来,但对我来说,问题是:

    • 在引用您的证书路径时,不要使用上传的 .pfx 文件名。相反,如前所述,您的证书文件将获得一个新名称“.p12”,并且(在 linux 容器中)位于“/var/ssl/private/”下。
    • 指定一个空白密码。不要为上传的 .pfx 文件指定密码。而是将 appsetting "IdentityServer__Key__Password" 设置为 ""(空)。

    【讨论】:

      【解决方案4】:

      .Net Clean ArchitectureDependencyInjection.cs 中调用services.AddIdentityServer() .AddApiAuthorization&lt;ApplicationUser, ApplicationDbContext&gt;();,这是扩展方法。这个方法在内部调用了一堆其他方法,其中一个是.AddSigningCredentials()。不幸的是,这种默认方法在 Linux 环境中会失败,因为它无法读取裸私钥。根据这个issue你需要在Linux中自己构建PFX。

      我认为的解决方案:

      1. 删除.AddApiAuthorization&lt;ApplicationUser, ApplicationDbContext&gt;();
      2. 编写自己的方法
      var bytes = File.ReadAllBytes($"/var/ssl/private/{thump_print_goes_here}.p12");
      var certificate = new X509Certificate2(bytes);
      var builder = services.AddIdentityServer()
                      .AddAspNetIdentity<ApplicationUser>()
                      .AddOperationalStore<ApplicationDbContext>()
                      .AddIdentityResources()
                      .AddApiResources()
                      .AddClients()
                      .AddSigningCredential(certificate);
      

      【讨论】:

      • 您好,您提到的错误已解决,我可以使用我上传到的证书的指纹,直接使用 appsettings.json 文件中设置的身份服务器证书的 .p12 文件我的天蓝色网络应用程序。
      • 你能分享你的代码吗?因为我无法直接在 appsettings.json 中使用 .p12 解决问题
      【解决方案5】:

      编辑 2 中提供的解决方案有效。我已经在 Azure 上的 Linux 应用服务中使用我的 .NET6 应用测试并验证了这一点。

      我的 appsettings.Production.json 看起来像这样:

      {
          "IdentityServer": {
              "Key": {
                  "Type": "File",
                  "FilePath": "/var/ssl/private/{thumbprintGoesHere}.p12",
                  "Password": ""
                }
          }
      }
      

      我已按照本指南 https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-5.0&tabs=visual-studio 在 Azure Key Vaults 中创建了我的 SSL 证书和 SigningKey 证书作为自签名密钥。我使用 WEBSITE_LOAD_CERTIFICATES 将证书加载到 /var/ssl/private/ 路径。

      我希望这可以帮助遇到同样问题的人。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-25
        • 2021-01-23
        • 2020-07-25
        • 2021-07-24
        • 2020-01-19
        • 2019-03-09
        • 1970-01-01
        • 2017-12-01
        相关资源
        最近更新 更多