代码之家  ›  专栏  ›  技术社区  ›  Sol

将证书持久化到docker容器中

  •  -1
  • Sol  · 技术社区  · 2 年前

    我使用的是Windows 11 Pro、.net 8和linux容器。 我使用C#在.net8中完成了Web API项目。 坚持使用证书时出现问题。 这是实际的GitHub repo=> https://github.com/xmione/AccSol . 这是我的docker撰写文件:

    version: '3.4'
    
    services:
      api:
        image: ${DOCKER_REGISTRY-}api
        build:
          context: .
          dockerfile: API/Dockerfile
        container_name: api
        hostname: api
        user: root
        depends_on:
          - sql
        volumes:
          - "./AccSol.EF/Data:/scripts"  
          - "./certs:/certs"
        # environment:
        #   - ASPNETCORE_ENVIRONMENT=Docker
        #   - DOTNET_ENVIRONMENT=Docker
        #   - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/mssql-tools/bin
        networks:
          accsolnet:   
      accsol.api:
        image: ${DOCKER_REGISTRY-}accsolapi
        volumes:
          - "./AccSol.EF/Data:/scripts"  
          - "./certs:/certs"
        build:
          context: .
          dockerfile: AccSol.API/Dockerfile
        container_name: accsolapi
        hostname: accsolapi
        user: root
        depends_on:
          - sql
        # environment:
        #   - ASPNETCORE_ENVIRONMENT=Docker
        #   - ASPNETCORE_URLS="http://+;https://+" 
        #   - ASPNETCORE_HTTP_PORT=5049
        #   - ASPNETCORE_HTTPS_PORT=7040
        #   - ASPNETCORE_Kestrel__Certificates__Default__Password="P@ssw0rd123" 
        #   - ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/AccSol.pfx      
        #   - DOTNET_ENVIRONMENT=Docker
        #   - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/mssql-tools/bin
        networks:
          accsolnet:   
      sql:
        image: ${DOCKER_REGISTRY-}accsolsqlserver
        build:
            context: .
            dockerfile: sqlserver-linux.df
        #image: "mcr.microsoft.com/mssql/server:2022-latest"
        container_name: accsolsqlserver
        hostname: accsolsqlserver
        user: root
        ports: # not actually needed, because the two services are on the same network
          - "14344:1433" 
        environment:
          - ACCEPT_EULA=y
          - MSSQL_SA_PASSWORD=P@ssw0rd123
        networks:
          accsolnet:   
    networks:
      accsolnet:
        name: accsolnet
        driver: bridge
        ipam:
          driver: default
          config:
            - subnet: 172.20.0.0/16
    

    这是我给AccSol的Dockerfile。API

    FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
    # Metadata indicating an image maintainer.
    LABEL maintainer="[email protected]"
    USER root
    ENV sa_password=P@ssw0rd123
    ENV server=accsolsqlserver
    ENV ACCEPT_EULA=Y
    #ENV PATH="/opt/mssql-tools/bin:${PATH}"
    ENV PATH="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/mssql-tools/bin"
    
    # Update package lists
    RUN apt-get update
    
    # Upgrade packages
    RUN apt-get upgrade -y
    
    # Install necessary packages
    RUN apt-get install -y inetutils-ping curl gnupg openssl
    
    # Install Microsoft GPG key
    RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
    
    # Add Microsoft repository
    RUN curl https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list
    
    # Update package lists again
    RUN apt-get update
    
    # Install MSSQL tools and dependencies
    RUN ACCEPT_EULA=Y apt-get install -y mssql-tools unixodbc-dev
    
    WORKDIR /certs
    # Generate the RSA key and certificate
    RUN openssl genrsa -out /certs/AccSol.key 4096
    RUN openssl req -new -x509 -text -key /certs/AccSol.key -out /certs/AccSol.cert
    
    # Convert the key and certificate to a PFX file
    RUN openssl pkcs12 -export -out /certs/AccSol.pfx -inkey /certs/AccSol.key -in /certs/AccSol.cert -password pass:P@ssw0rd123
    
    
    # Copy the script to the container
    COPY ./start.sh /start.sh
    
    # Make the script executable
    RUN chmod +x /start.sh
    
    # Run the script when the container starts
    CMD /start.sh
    
    WORKDIR /app
    EXPOSE 5049
    EXPOSE 7040
    
    FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
    # Metadata indicating an image maintainer.
    LABEL maintainer="[email protected]"
    ARG BUILD_CONFIGURATION=Release
    # Set the ASPNETCORE_ENVIRONMENT environment variable
    ENV ASPNETCORE_ENVIRONMENT=Docker
    CMD [ "printenv", "ASPNETCORE_ENVIRONMENT" ]
    
    ENV DOTNET_ENVIRONMENT=Docker
    CMD [ "printenv", "DOTNET_ENVIRONMENT" ]
    
    ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/mssql-tools/bin
    ENV ASPNETCORE_URLS="http://+;https://+" 
    ENV ASPNETCORE_HTTP_PORT=5049
    ENV ASPNETCORE_HTTPS_PORT=7040
    ENV ASPNETCORE_Kestrel__Certificates__Default__Password="P@ssw0rd123" 
    ENV ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/AccSol.pfx      
    
    WORKDIR /src
    COPY ["AccSol.API/AccSol.API.csproj", "AccSol.API/"]
    COPY ["AccSol.EF/AccSol.EF.csproj", "AccSol.EF/"]
    RUN dotnet restore "./AccSol.API/./AccSol.API.csproj"
    COPY . .
    WORKDIR "/src/AccSol.API"
    RUN dotnet build "./AccSol.API.csproj" -c $BUILD_CONFIGURATION -o /app/build
    
    FROM build AS publish
    ARG BUILD_CONFIGURATION=Release
    RUN dotnet publish "./AccSol.API.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
    
    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    
    ENTRYPOINT ["dotnet", "AccSol.API.dll"]
    

    这是我的AccSol。API程序.cs

    using AccSol.EF.Data;
    using AccSol.EF.Repositories;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using System.Diagnostics;
    using System.Net;
    
    internal class Program
    {
        private static ILogger _logger;
        private static string _environmentName;
        private static string _connectionString;
        private static WebApplication? _app;
        private static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
    
            //builder.WebHost.UseUrls("https://localhost:7040");
            //builder.WebHost.ConfigureKestrel(options =>
            //{
            //    options.Listen(IPAddress.Loopback, 7040, listenOptions =>
            //    {
            //        var config = builder.Configuration.GetSection("Kestrel:Certificates:Development");
            //        string certsPath = config.GetSection("CertsPath").Value ?? string.Empty;
            //        string password = config.GetSection("Password").Value ?? string.Empty;
            //        listenOptions.UseHttps(certsPath, password);
            //    });
            //});
            var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
            _logger = loggerFactory.CreateLogger<Program>();
    
            var environment = builder.Environment;
            _environmentName = environment.EnvironmentName;
            var isDevelopment = environment.IsDevelopment();
    
            builder.Configuration
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{_environmentName}.json", optional: true, reloadOnChange: true);
    
            builder.Services.AddLogging(configure => configure.AddConsole());
    
            // Add services to the container.
            builder.Services.AddCors(c =>
            {
                c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
            });
    
            _connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
    
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(_connectionString, b => b.MigrationsAssembly("AccSol.EF")), ServiceLifetime.Scoped);
    
            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            builder.Services.AddScoped<IRepositoryManager, RepositoryManager>();
    
    
            _app = builder.Build();
    
            // Configure the HTTP request pipeline.
            if (isDevelopment)
            {
                //DoDevelopment();
            }
    
            _app.UseHttpsRedirection();
    
            _app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
    
            _app.UseAuthorization();
    
            _app.MapControllers();
    
            _app.Run();
        }
        private static void DoDevelopment()
        {
    
            _logger.LogInformation($"Application is in {_environmentName}.");
    
            // migrate any database changes on startup (includes initial db creation)
            // Important!: This will automatically create the database in case missing.
            //           : No need to switch to master database.
            //Start - Automatically update db from new migrations ====================>
            try
            {
                var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
                    .UseSqlServer(_connectionString)
                .Options;
    
                _logger.LogInformation("ApplicationDbContext is initializing...");
                var context = new ApplicationDbContext(contextOptions);
    
                _logger.LogInformation("Trying to connect to the Database using sqlcmd...");
                // Use sqlcmd to run a SQL script, for example, assuming you have a script named "MyScript.sql" in the root of your project
                //var scriptPath = "MyScript.sql";
    
                // Modify the sqlcmd command as needed based on your SQL Server setup
                //var sqlCmdCommand = $"sqlcmd -S localhost -d master -U sa -P P@ssword123 -i {scriptPath}";
                //var sqlCmdCommand = $"sqlcmd -S localhost -d master -U sa -P P@ssword123 -Q 'SELECT * FROM sys.objects'";
                var sqlCmdCommand = $"/bin/bash -c \"sqlcmd -S localhost,14344 -d master -U sa -P P@ssword123 -Q 'SELECT * FROM sys.objects'\"";
    
                // Start the process
                var processStartInfo = new ProcessStartInfo
                {
                    FileName = "/bin/bash",
                    RedirectStandardInput = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    Arguments = $"-c \"{sqlCmdCommand}\""
                };
    
                using (var process = new Process { StartInfo = processStartInfo })
                {
                    process.Start();
    
                    // You can read the output and error streams if needed
                    var output = process.StandardOutput.ReadToEnd();
                    var error = process.StandardError.ReadToEnd();
    
                    process.WaitForExit();
    
                    if (process.ExitCode != 0)
                    {
                        _logger.LogInformation($"Error running sqlcmd. Exit code: {process.ExitCode}");
                        _logger.LogInformation($"Output: {output}");
                        _logger.LogInformation($"Error: {error}");
                    }
                    else
                    {
                        _logger.LogInformation("sqlcmd executed successfully.");
                    }
                }
    
                _logger.LogInformation("Database.Migrate() has been called...");
                context.Database.Migrate();
    
                _logger.LogInformation("Database migration completed successfully.");
    
                //End   - Automatically update db from new migrations ====================>
            }
            catch (Exception ex)
            {
                    _logger.LogError($"Database migration failed. Error: {ex.Message}");
                throw;
            }
    
            _app.UseSwagger();
            _app.UseSwaggerUI();
        }
    }
    

    编辑:我尝试通过装载卷来持久化证书,并使用dotnet-dev-certs命令来创建和信任它,并使用ASPNETCORE_ENVIRONMENT变量来设置路径和端口。我可以在隔离=进程中轻松完成,但不能在hyperv模式下完成。我用的是EF Core。我认为EF导致这个错误是因为它试图连接到accsolsqlserver容器,但证书没有持久化。我可以在隔离=进程中轻松完成,但不能在hyperv模式下完成。目前,我收到以下错误:

    ERROR [accsol.api base 11/15] RUN openssl req -new -x509 -text -key /certs/client.key -out /certs/client.cert  
    

    如果我尝试删除openssl代码来创建证书,我会在运行以下命令的.bat文件中运行docker compose-up:

    cmd /c "dotnet dev-certs https -ep certs\AccSol.pfx -p P@ssw0rd123"
    cmd /c "dotnet dev-certs https --trust"
    docker-compose --verbose up --build
    

    现在,当我这样做时,这就是我的错误:

    accsolapi        |       Hosting failed to start
    accsolapi        |       System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
    accsolapi        |       To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.
    

    所以我的问题是,如何使用docker-compose.yml文件和Dockerfile,在Windows 11 Pro中使用aspnet核心web api、.net 8框架的linux docker容器中持久保存证书?

    1 回复  |  直到 2 年前
        1
  •  0
  •   Sol    2 年前

    经过两天的绞尽脑汁和用头敲击键盘,我找到了解决方案。 首先,在我的appSettings文件中,我需要添加一个设置来测试与master数据库的连接,然后再连接到应用程序的数据库。这是因为最初并没有应用程序数据库,它只能通过运行数据库来创建。迁移()。 这是我的appSettings.json

    {
      "ConnectionStrings": {
        "TestConnection": "Server=accsolsqlserver;Database=master;User Id=sa;Password=P@ssw0rd123;TrustServerCertificate=True;", //additional setting
        "DefaultConnection": "Server=accsolsqlserver;Database=aspnet-AccSol-4e738ad6-a4fd-4a62-8afa-6641a1ee333c;User Id=sa;Password=P@ssw0rd123;TrustServerCertificate=True;"
    },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning",
          "Microsoft.Hosting.Lifetime": "Information"
        }
      },
      "AllowedHosts": "*",
      "APIBaseURL": "https://localhost:7040/",
      "HstsOptions": {
        "IncludeSubDomains": true,
        "Preload": true,
        "MaxAge": 31536000
      },
      "Kestrel": {
        "Certificates": {
          "Development": {
            "CertsPath": "./certs/AccSol.pfx",
            "Password": "P@ssw0rd123"
          }
        }
      }
    }
    

    其次,我在Program.cs中为证书添加了代码。

    var config = builder.Configuration.GetSection("Kestrel:Certificates:Development");
    string certsPath = config.GetSection("CertsPath").Value ?? string.Empty;
    string password = config.GetSection("Password").Value ?? string.Empty;
    
    builder.WebHost.UseUrls(_baseAPIUrl);
    builder.WebHost.ConfigureKestrel(options =>
    {
        //TODO: sol: these ports need to be in the appSettings file
        options.Listen(IPAddress.Loopback, 5049); // HTTP
        options.Listen(IPAddress.Loopback, 7040, listenOptions =>
        {
            listenOptions.UseHttps(certsPath, password);
        });
    });
    

    第三,我确保Dockerfile中的这些环境变量位于最后一层,包括端口和证书路径。

    FROM base AS final
    WORKDIR /app
    COPY --from=publish /app/publish .
    COPY --from=build /src/AccSol.API/certs /certs
    
    ENV ASPNETCORE_ENVIRONMENT=DockerSql
    ENV ASPNETCORE_URLS=http://+:5049;https://+:7040
    ENV ASPNETCORE_Kestrel__Certificates__Default__Password=P@ssw0rd123
    ENV ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/AccSol.pfx
    
    EXPOSE 5049
    EXPOSE 7040
    ENTRYPOINT ["dotnet", "AccSol.API.dll"]
    

    第四,我检查了docker-compose.yml文件中是否也声明了端口。我在某个地方读到没有必要这样做,但我仍然需要它,因为如果没有,它将在Docker Desktop中创建新的端口。

      accsol.api:
        image: ${DOCKER_REGISTRY-}accsolapi
        volumes:
          - "./AccSol.EF/Data:/scripts"  
          - "./certs:/certs"
        build:
          context: .
          dockerfile: AccSol.API/Dockerfile
        container_name: accsol.api
        hostname: accsol.api
        user: root
        ports:
           - "5049:5049"  
           - "7040:7040"  
        depends_on:
          - sql
        environment:
          - ASPNETCORE_ENVIRONMENT=DockerSql
          - ASPNETCORE_URLS=http://+:5049;https://+:7040
          - ASPNETCORE_Kestrel__Certificates__Default__Password=P@ssw0rd123 
          - ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/AccSol.pfx      
          - PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/mssql-tools/bin
        networks:
          accsolnet:
    

    之后,api容器(accsol.api)能够连接到sql服务器容器(accsolsqlserver),它通过运行我的CreateInitialData.sql文件自动在数据库中创建初始数据。

    所以这个问题现在已经解决了,但另一个问题出现了。 当我运行dockerrun时,我能够浏览api swagger页面并运行这些方法,没有任何问题。使用docker compose,我得到了ERR_CONNECTION_CLOSED。这是另一个问题,我已经在Docker社区论坛上创建了一个关于这个问题的帖子。 Here is the link to that post . ERR_CONNECTION_CLOSED