ASP.NET
Core Identity
Server 4 Docker
We are
going to dockerize the services to containers so far, including of
l Backend Server
l Auth Server (Idsrv4)
l Redis
l OpenLdap
l Nginx (Optional)
This
article won’t show details of how to write dockerfile or docker-compose file
but only tips and traps (lol).
If you
would like to get started with Docker, you can take a look at my Docker ebook
: )
▋Docker 18.05.0-ce
▋ASP.NET Core 3.0.100
The
source code is on my Github.
▋Architecture
The
services’ architecture after dockerized is as following. Each block presents a
container.
My
project’s directories and files:
Directory/File
|
Description
|
/docker
|
/build
|
/auth
|
Auth Server’s build binaries
|
/Backend
|
Backend’s build binaries
|
/certs
|
Docker.crt
|
Self-Signed certificate by OpenSSL
|
Docker.key
|
Docker.pfx
|
.env
|
|
Environment file for Compose
|
auth.dockerfile
|
|
Dockerfile for Auth Server
|
backend.dockerfile
|
|
Dockerfile for Backend Server
|
docker-compose.yml
|
|
Docker Compose file
|
/src
|
|
|
Source code
|
▋Build
I build
the project at local without using a dotnet-core build container.
▋Auth Server
$ cd src/AspNetCore.IdentityServer4.Auth
$ dotnet publish --output ../../docker/build/auth --configuration release
▋Backend
$ cd src/AspNetCore.IdentityServer4.WebApi
$ dotnet publish --output ../../docker/build/backend --configuration release
▋Self-signed Certificate
Use the
following commands to create certificate. Notice that we only need PFX for
Kestrel (Auth/Backend Server) so far. The CRT and KEY will be used on Nginx
later, so do not delete them.
$ cd docker/certs
$ openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout Docker.key -x509 -days 3650 \
-out Docker.crt
$ openssl pkcs12 -export -out certs/jb.pfx -inkey Docker.key -in Docker.crt
▋Dockerfile
Here is
the Dockerfile for Backend,
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0 AS runtime
# Arg for current environment, eg. Development, Docker, Production. Use docker-compose file to overwrite.
ARG env="Docker"
WORKDIR /app
VOLUME /app/App_Data/Logs
RUN mkdir -p /etc/docker/certs/
COPY ./certs/${env}.pfx /etc/docker/certs/
RUN mv /etc/docker/certs/${env}.pfx /etc/docker/certs/docker.pfx
COPY ./build/backend ./
ENV TZ "Asia/Taipei"
ENV ASPNETCORE_URLS "http://+:5000;https://+:5001"
ENV ASPNETCORE_ENVIRONMENT ${env}
ENV ASPNETCORE_Kestrel__Certificates__Default__Password ""
ENV ASPNETCORE_Kestrel__Certificates__Default__Path "/etc/docker/certs/docker.pfx"
EXPOSE 5000 5001
ENTRYPOINT ["dotnet", "AspNetCore.IdentityServer4.WebApi.dll"]
Key
points:
l The argument (ARG): env,
is set as “Docker” in default. However, we can overwrite it in Compose file
with the environment variable from .env file => we will see it later.
l Copy the PFX into container and rename it as docker.pfx.
l Set Kestrel’s listening ports by environment variable: ASPNETCORE_URLS.
l Set Kestrel’s SSL Certificate by environment variables:
n ASPNETCORE_Kestrel__Certificates__Default__Password
n ASPNETCORE_Kestrel__Certificates__Default__Path
▋Environment File for Compose (.env)
Notice
that the custom environment variable: DOCKER_ENV,
will be set to ASPNETCORE_ENVIRONMENT
for Kestrel and copy the mapping
Certificate (e.q. Docker.crt) to container.
▋.env
COMPOSE_PROJECT_NAME=idsrv4
DOCKER_ENV=Docker
LDAP_PORT=389
REDIS_PORT=6379
BACKEND_PORT=5000
BACKEND_HTTPS_PORT=5001
AUTH_PORT=6000
AUTH_HTTPS_PORT=6001
▋Compose file
Now we will use Docker Compose to run the services:
l Backend Server
l Auth Server (Idsrv4)
l Redis
l OpenLdap
Notice that we have to mount OpenLdap’s database and
configuration from
- /var/lib/ldap/
- /etc/ldap/slapd.d
to host in order to keeping the LDAP entities/settings.
For Redis, mount /data
out.
|
▋docker-compose.yml
version: "3"
services:
openldap:
image: osixia/openldap:stable
container_name: idsrv-openldap
volumes:
- openldap_database:/var/lib/ldap
- openldap_slapd:/etc/ldap/slapd.d
ports:
- ${LDAP_PORT}:389
expose:
- 389
redis:
image: redis:latest
container_name: idsrv-redis
volumes:
- redis_data:/data
ports:
- ${REDIS_PORT}:6379
expose:
- 6379
auth:
image: idsrv4-auth:latest
build:
context: .
dockerfile: auth.dockerfile
args:
env: ${DOCKER_ENV}
container_name: idsrv-auth
networks:
- default
ports:
- ${AUTH_PORT}:6000
- ${AUTH_HTTPS_PORT}:6001
volumes:
- ../Logs/Auth:/App_Data/Logs
depends_on:
- openldap
- redis
backend:
image: idsrv4-backend:latest
build:
context: .
dockerfile: backend.dockerfile
args:
env: ${DOCKER_ENV}
container_name: idsrv-backend
networks:
- default
ports:
- ${BACKEND_PORT}:5000
- ${BACKEND_HTTPS_PORT}:5001
volumes:
- ../Logs/Backend:/App_Data/Logs
depends_on:
- openldap
- redis
networks:
default:
driver: bridge
volumes:
openldap_database:
openldap_slapd:
redis_data:
Now we
can build Docker images and run the services by Compose file,
$ cd docker
$ docker-compose build [--no-cache]
$ docker-compose up -d
Here are
the running containers with the Compose file…
▋(optional) Architecture with Nginx
We can
put a Nginx Reverse Proxy container to proxy the web server(s). For example,
My
project’s directories and files will be updated as following:
Directory/File
|
Description
|
/docker
|
/build
|
/auth
|
Auth Server’s build binaries
|
/Backend
|
Backend’s build binaries
|
/certs
|
Docker.crt
|
Self-Signed certificate by OpenSSL
|
Docker.key
|
Docker.pfx
|
/nginx
|
web-servers.conf
|
Nginx config for sites-enabled
|
.env
|
|
Environment file for Compose
|
auth.dockerfile
|
|
Dockerfile for Auth Server
|
backend.dockerfile
|
|
Dockerfile for Backend Server
|
nginx.dockerfile
|
|
Dockerfile for Nginx
|
docker-compose.yml
|
|
Docker Compose file
|
/src
|
|
|
Source code
|
▋web-servers.conf
Nginx will
need CRT and KEY that we created in the first step for setting SSL Certificate.
# Internal host(s)
upstream backend {
server backend:5001;
}
ssl on;
ssl_certificate /etc/docker/certs/docker.crt;
ssl_certificate_key /etc/docker/certs/docker.key;
# Proxy backend server listens on port 5000,5001
server {
listen 5000;
listen 5001 ssl;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 600s;
proxy_pass https://backend;
}
}
▋Dockerfile:
nginx.dockerfile
FROM nginx:1.17.5
ARG env="Docker"
COPY ./nginx/web-servers.conf /etc/nginx/sites-available/web-servers.conf
COPY ./certs/${env}.crt /etc/docker/certs/
COPY ./certs/${env}.key /etc/docker/certs/
RUN mv /etc/docker/certs/${env}.crt /etc/docker/certs/docker.crt
RUN mv /etc/docker/certs/${env}.key /etc/docker/certs/docker.key
RUN ln -s /etc/nginx/sites-available/web-servers.conf /etc/nginx/conf.d/web-servers.conf
ENV TZ "Asia/Taipei"
EXPOSE 80 5000 5001
▋Compose file
(Updated)
Update
the Compose file by adding Nginx service and remove expose ports from Backend
Service.
The below
YML file only shows the updated part:
version: "3"
services:
# ...skip openldap, redis, auth
backend:
image: idsrv4-backend:latest
build:
context: .
dockerfile: backend.dockerfile
args:
env: ${DOCKER_ENV}
container_name: idsrv-backend
networks:
- default
# ports:
# - ${BACKEND_PORT}:5000
# - ${BACKEND_HTTPS_PORT}:5001
volumes:
- ../Logs/Backend:/App_Data/Logs
depends_on:
- openldap
- redis
nginx:
image: idsrv4-nginx:latest
container_name: idsrv-nginx
build:
context: .
dockerfile: nginx.dockerfile
args:
env: ${DOCKER_ENV}
networks:
- default
ports:
- 80:80
- ${BACKEND_PORT}:5000
- ${BACKEND_HTTPS_PORT}:5001
depends_on:
- auth
- backend
# ... skip networks, volumes configuration
Here are
the running containers with the Compose file…
▋Wait a minute! Where
re the TRAPs?
Yeah, the
most important part is the TRAPS!
▋SSL Connection
cannot be established from Backend to Auth Server
While we
used Self-signed certificate, the following connection problem had a great
chance to occur!
{
"error": "Error
connecting to https://auth:6001/.well-known/openid-configuration. The SSL
connection could not be established, see inner exception.."
}
|
Method 1.
To make
the HttpClient on Backend connect to Auth Server’s API with untrusted
certificate, we can define the HttpClientHandler NOT to validate the server
certificate.
For
example,
services.AddHttpClient("AuthHttpClient")
.ConfigurePrimaryHttpMessageHandler(h =>
{
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
return handler;
});
Method 2.
The other
way is to trust the Self-signed certificate on Backend Server.
First
install ca-certificates,
$ apt-get install ca-certificates
Now we can
include the certificate,
$ cp /etc/docker/$CERT /usr/share/ca-certificates
$ dpkg-reconfigure ca-certificates
Choose
option “3. ask” and select the
Self-signed certificate.
Then
select the Self-signed certificate, (e.q. Certificates to activate: 1)
Which will result in
Last
step, we need to tell update-ca-certificates explicitly to activate
the cert by adding it to
/etc/ca-certificate.conf or /etc/ca-certificate/update.d
and copy
the certificate to
/usr/local/share/ca-certificates
$ CERT=Docker.crt
$ echo "+$CERT" >/etc/ca-certificates/update.d/activate_my_cert
$ cp /etc/docker/$CERT /usr/local/share/ca-certificates/
$ update-ca-certificates
Results:
It’s done
but there is one issue left:
Notice that we use the container name:
“auth”, in Backend’s appsettings as the Auth Server’s base url:
"Host": {
"AuthServer": "https://auth:6001",
}
So the Self-signed certificate’s
CN must matches it or the SSL connection will still be failed!
Take curl’s message for example,
Change the base url to 192.168.99.100
or use the right CN can solve this issue.
|
沒有留言:
張貼留言