Identity Server - Dockerization - Part 2

  1. Identity Server failure
    Npgsql.NpgsqlException (0x80004005): Exception while connecting
     is4_1      |  ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException (99): Cannot assign requested address [::1]:5432
    

    NOTE: localhost:5432 is unresolvable in container, which can only resolve db service

  2. Add one more environment variable in docker-compose.override.yml to overwrite ConnectionStrings:DefaultConnection in appSettings.json
    is4:
      environment:
        - ConnectionStrings:DefaultConnection=Server=db;Port=5432;Database=IS4Database;User Id=postgres;Password=P@ssword!
    

    Identity Server container will start working😆

  3. Another exception will throw after navigating to Privacy page from Client app
    Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
     client_1   |       An unhandled exception has occurred while executing the request.
     client_1   | System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
     client_1   |  ---> System.IO.IOException: IDX20804: Unable to retrieve document from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
     client_1   |  ---> System.Net.Http.HttpRequestException: Cannot assign requested address
     client_1   |  ---> System.Net.Sockets.SocketException (99): Cannot assign requested address
     client_1   |    at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
     client_1   |    --- End of inner exception stack trace ---
    

    NOTE: Client needs to communicate to Identity Provider’s well-known endpoint and validate access token, however, it couldn’t recognize localhost:5000 but is4

  4. Here is the most tricky step:
    • In Client project, update MetadataAddress to load well-known configuration from is4 instead of localhost:5000
      // Startup.cs:
      public void ConfigureServices(IServiceCollection services)
      {
        ......
        var containerHost = Configuration.GetValue<string>("IdentityServer:ContainerHost");
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "cookie";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("cookie")
        .AddOpenIdConnect("oidc", options =>
        {
            ......
            // DEV ONLY
            // Container Identity Server replace well-known endpoint
            // e.g. Replace http://is4 to http://localhost:5000, as localhost is unknown
            options.MetadataAddress = $"{containerHost}/.well-known/openid-configuration";
        });
      
    • In Client project, replace the redirect Url from is4 to localhost:5000
      public void Configure(IApplicationBuilder app)
      {
        ......
        // DEV ONLY
        // Container Identity Server Redirection
        // e.g. Replace http://is4 to http://localhost:5000
        app.Use(async (httpcontext, next) =>
        {
            await next();
            if (httpcontext.Response.StatusCode == StatusCodes.Status302Found)
            {
                var containerHost = Configuration.GetValue<string>("IdentityServer:ContainerHost");
                var authority = Configuration.GetValue<string>("IdentityServer:Authority");
      
                if (!containerHost.Equals(authority, StringComparison.OrdinalIgnoreCase))
                {
                    string location = httpcontext.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Location];
                    httpcontext.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Location] =
                            location.Replace(containerHost, authority);
                }
      
            }
        });
        ......
      }
      

      NOTE: Without this step, browser will redirect to https://is4, which can’t be resolved by browser

  5. Update environment variables in docker-compose.override.yml
    client:
      environment:
        - IdentityServer:Authority=https://localhost:5000
        - IdentityServer:ContainerHost=https://is4
    
  6. Certificate validation failure for domain is4
      Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
        An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: IDX20803: Unable to obtain configuration from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
        ---> System.IO.IOException: IDX20804: Unable to retrieve document from: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
        ---> 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.
    

    NOTE: As mentioned in Identity Server - Dockerization (1) #3, the certificate ONLY allows localhost, therefore the certificate is invalid for is4

  7. Genereate self-signed certificate
    • Add configuration file e.g. “is4-container-cert.conf” to ./Certificates folder
       [req]
       distinguished_name = req_distinguished_name
       req_extensions     = req_ext
       x509_extensions    = v3_ca
      
       [req_distinguished_name]
       commonName                  = jaylin
       commonName_default          = localhost
       commonName_max              = 64
      
       [req_ext]
       subjectAltName = @alt_names
       1.3.6.1.4.1.311.84.1.1=ASN1:UTF8String:Something
      
       [v3_ca]
       subjectAltName = @alt_names
       basicConstraints = critical, CA:false
       keyUsage = keyCertSign, cRLSign, digitalSignature,keyEncipherment
      
       [alt_names]
       DNS.1 = localhost
       DNS.2 = 127.0.0.1
       DNS.3 = is4
       DNS.4 = api
      
    • Open bash command and navigate to Certificates folder
    • Run openssl command to generate Linux certificate
      openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./is4-container-cert.key -out ./is4-container-cert.crt -config ./is4-container-cert.conf
      
    • Run openssl command to generate Windows certificate
      openssl pkcs12 -export -out ./is4-container-cert.pfx -inkey ./is4-container-cert.key -in ./is4-container-cert.crt
      
  8. Update Dockerfile for each project to install crt in container
    FROM base AS final
    COPY ["./Certificates/is4-container-cert.crt", "/usr/local/share/ca-certificates/is4-container-cert.crt"]
    RUN chmod 644 /usr/local/share/ca-certificates/is4-container-cert.crt
    RUN update-ca-certificates
    WORKDIR /app
    COPY --from=publish /app/publish .
    ENTRYPOINT ["dotnet", "IdentityProvider.dll"]
    
  9. Update Kestrel environment variables and volumes in docker-compose.override.yml for api, client and is4 services
    client:
      environment:
        - ASPNETCORE_Kestrel__Certificates__Default__Password=P@ssword!
        - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/is4-container-cert.pfx
      volumes:
        - .\Certificates:/https/
    

    It will create a volume to map the Certificates folder, and Kestrel Password comes from #7.4 when generating pfx certificate.

  10. Install pfx certificate on Windows
    • Right Click on is4-container-cert.pfx
    • Install PFX
    • Current User
    • Fill Password
    • Place all certificates in the following store “Trusted Root Certification Authorities”
  11. Run docker-compose up --build
  12. Browse https://localhost:5002 Client Home
  13. Navigate to Privacy, it will redirect to Identity Server to login (username / password: scott / Password123!) Identity Server Login
  14. After login successfully, it will redirect back to Privacy page Client Privacy
  15. Click on Call Api button
  16. API access authorized! should return