Facing the Problem
In case of dockerizing .NET applications using SQL server, one has to think about the way to connect to the database.
Fortunately, Microsoft provides us with a couple of opportunities to establish secure and robust connections which can be applied to Windows containers as well. However, in case of connecting out of a Linux container, not all these concepts are available out of the box. This also affects the usage of integrated security for establishing a connection from an already authenticated client (e.g.: Single Sign On systems).
This article applies the concept of integrated security, which is built on top of a Kerberos authentication process, for Linux containers. The solution requires no code changes in .NET Core application. Instead, it illustrates docker image preparations and configuration of kerberos authentication on system level. At the end, you can connect via integrated security to SQL Server out of a previously authenticated Linux container.
Discovering the Solution Step by Step
For this solution, we have to go down a longer road and pick up pieces of a bigger puzzle, before we are able to create a solution.
Prerequisites
Things you need to reconstruct the solution in your environment:
- Docker host (e.g.: docker on windows, docker tool box...)
- SQL Server instance with integrated security enabled (on premise or even dockerized)
- Domain Controller (AD acting as Key Distribution Server)
Our Demo Application
In order to verify the DB connection, I created a very simple solution. The application hangs in an endless loop querying a particular table dat.MyDataTable
in the database myDataBase
of the SQL Server instance myDataBaseServer
. Retrieved result sets (I am assuming that MyDataTable
contains at least three columns) are printed as console output and can be viewed by attaching to container.
using System;
using System.Data.SqlClient;
namespace MyApplication
{
class Program
{
static void Main(string[] args)
{
while (true)
{
try
{
using (var connection = new SqlConnection("Server=tcp:myDataBaseServer,46005;
Initial Catalog=myDataBase;Integrated Security=true;;"))
{
var command = new SqlCommand
("SELECT TOP 10 * FROM dat.myDataTable", connection);
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
Console.WriteLine($"{reader[0]}:{reader[1]} ${reader[2]}");
}
}
}
}
catch (Exception ex)
{
Console.Write(ex);
}
System.Threading.Thread.Sleep(10000);
}
}
}
}
Please consider the connection string specifying integrated security:
"Server=tcp:myDataBaseServer,46005;Initial Catalog=myDataBase;Integrated Security=true;;"
Prepare Kerberos Authentication in Container
In order to communicate out of Linux containers with a Key Distribution Center (KDC), some preparations of container image and configuration are necessary.
- Install packages for KDC consumption.
- Creating a proper krb5.conf file for accessing KDC application hosted in AD Domain controller
- Generate keytab file (in order to avoid exposing passwords in plain text)
Required Packages for KDC Consumption
The demo application is running on the microsoft/dotnet:aspnetcore-runtime
image which basically derives from the debian:stretch-slim
image. This requires us to install the following packages (code pasted from underlying dockerfile
):
...
RUN apt install -y krb5-config
RUN apt-get install -y krb5-user
...
These pacakges enable us to run kinit
command within the container for fetching kerberos tickets from the KDC. Additionally, also the ktutil
tool is available for creating the above mentioned keytab
files.
Creating a Proper krb5.conf File
In order to communicate with the underlying KDC, a proper krb5.conf file is created and stored in the containers /etc folder. For constructing a proper example, assume we are in the domain my.company.local
and our KDC is given by mydomaincontroller.my.company.local
(in a windows AD driven environment, you can get the value of mydomaincontroller
by simply running the command echo %logonserver%
when you are logged in via AD). One has to also keep in mind that the KDC is located in the Domain Controller as and using the AD database.
[appdefaults]
default_lifetime = 25hrs
krb4_convert = false
krb4_convert_524 = false
ksu = {
forwardable = false
}
pam = {
minimum_uid = 100
forwardable = true
}
pam-afs-session = {
minimum_uid = 100
}
[libdefaults]
default_realm = MY.COMPANY.LOCAL
ticket_lifetime = 25h
renew_lifetime = 7d
forwardable = true
noaddresses = true
allow_weak_crypto = true
rdns = false
[realms]
MY.COMPANY.LOCAL = {
kdc = mydomaincontroller.my.company.local
default_domain = my.company.local
}
[domain_realm]
my.company.local = MY.COMPANY.LOCAL
[logging]
kdc = SYSLOG:NOTICE
admin_server = SYSLOG:NOTICE
default = SYSLOG:NOTICE
The krb5.conf file has to be located in the containers at /etc/kerb5.conf.
Generating a keytab File
In order to avoid passing passwords to kinit
command (e.g.: executing kinit username
and subsequently entering the password), I decided to generate proper keytab
files. These files are used for fetching kerberos tickets without requiring the password in plain text. In order to generate such a keytab
, you need to run the following commands in a Linux shell:
ktutil
ktutil: add_entry -password -p myUserName@MY.COMPANY.LOCAL -k 1 -e RC4-HMAC
ktutil: write_kt myUserName.keytab
ktuilt: extit
The received keytab
file can be mapped or copied in the container. For requesting a kerberos ticket under usage of the keytab
file, you can run:
kinit myUserName -k -t myUserName.keytab
By running klist
, you can see that a kerberos ticket was received.
Dockerize the Demo Application
Now we are far enough to look at the docker file:
FROM microsoft/dotnet:sdk AS build-env
WORKDIR /app
COPY *.csproj ./
RUN dotnet restore
COPY . ./
RUN dotnet publish -c Release -o out
FROM microsoft/dotnet:aspnetcore-runtime
WORKDIR /app
COPY --from=build-env /app/out .
RUN apt-get update
RUN apt-get remove krb5-config krb5-user
RUN apt install -y krb5-config
RUN apt-get install -y krb5-user
COPY krb5.conf /etc/krb5.conf
COPY myUserName.keytab /app/myUserName.keytab
COPY launch.sh /launch.sh
ENTRYPOINT /launch.sh
The job of the dockerfile
can be described by:
- Building the .NET Core application in a container based on
microsoft/dotnet:sdk AS build-env
image - Copying the build artefacts to a runtime image (from
microsoft/dotnet:aspnetcore-runtime
) - Installing kerberos packages for
debian:stretch-slim
image (is the base of microsoft/dotnet:aspnetcore-runtime
) - Copying the krb5.conf file and
keytab
- Copying a script that executes
kinit
and launches the .NET application - Specifying the launch script as entry point
The launch script launch.sh has to contain the kerberos authentication (kinit
command) and the execution of the application:
...
kinit myUserName-k -t myUserName.keytab
dotnet MyApplication.dll
Handle Expiration of Kerberos Tickets
Each kerberos ticket, received via kinit
command, has an expiration date. In order to avoid losing authentication while the container is running, one has to call kinit
again before the ticket has expired. In other words, kinit
has to be executed periodically over container's lifetime.
Handle Expiration of Keytab Files
Similar to keberos tickets, keytab
files will expire. You have to create and provide new keytab
files also in a periodical manner. A possible option would be to create the file during containers startup process and map it into the containers file system. Nevertheless, depending on the underlying environment, there may be other options as well.
Run It
For running the application, you have to create (use dockerfile
above) an image and run a container. When you attach to the docker container, you can see the result of the DB query, which is written as console output.
History
- 10th January, 2019: Article created