在带有 IdentityServer4 认证/http 错误的 azure Web 应用容器上部署 .net Core 3 linux 容器

J K*_*ing 5 azure docker azure-devops identityserver4 azure-pipelines

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

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

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

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

应用程序启动异常 System.InvalidOperationException:在 Microsoft.AspNetCore.ApiAuthorization.IdentityServer.SigningKeysLoader.LoadFromStoreCert(String subject, String storeName, StoreLocation storeLocation, DateTimeOffset currentTime)

我做了什么:

  1. 按照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与证书的“拇指”值

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

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

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

  "IdentityServer": {
    "Key": {
      "Type": "Store",
      "StoreName": "My",
      "StoreLocation": "CurrentUser",
      "Name": "CN=localhost"
    }
  }
Run Code Online (Sandbox Code Playgroud)

有人可以帮我弄清楚如何解决这个错误吗?

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

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

  "IdentityServer": {
    "Key": {
      "Type": "File",
      "FilePath": "aspnetapp.pfx",
      "Password": "Your_password123"
    }
  }
Run Code Online (Sandbox Code Playgroud)

现在我收到这个错误:

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

我将其添加到 dockerfile 中:

WORKDIR /app
COPY ["/aspnetapp.pfx", "/app"]
RUN find /app
Run Code Online (Sandbox Code Playgroud)

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

在此处输入图片说明

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

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

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

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

  "IdentityServer": {
    "Key": {
      "Type": "File",
      "FilePath": "/var/ssl/private/<thumb_value>.p12",
      "Password": "Your_password123"
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

加载证书时出错。密码不正确或进程没有权限将密钥存储在 Keyset 'EphemeralKeySet' Interop+Crypto+OpenSslCryptographicException: error:23076071:PKCS12routines:PKCS12_parse:mac verify failure

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

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

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

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

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

我将此代码添加到我的 statup.cs configureservices(我知道对这些值进行硬编码不是最佳实践,但我只想看看它是否可以加载证书,我将切换到 env 变量和密钥库):

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

        services.AddIdentityServer().AddSigningCredential(cert);
Run Code Online (Sandbox Code Playgroud)

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

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

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

编辑 5

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

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

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

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

tnc*_*997 5

原答案

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

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

  1. 在 Azure 门户中,从左侧菜单中选择应用服务 > <app-name>。
  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);
    
    Run Code Online (Sandbox Code Playgroud)

签署凭证

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

  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 门户中,从左侧菜单中选择应用服务 > <app-name>。
    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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

干净的架构

下面的代码示例显示了一个完整的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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)