Docker, AspNetCore, DB connection string best practices
Asked Answered
S

2

5

I've been spending the last week or so attempting to learn docker and all the things it can do, however one thing I'm struggling to get my head around is the best practice on how to manage secrets, especially around database connection strings and how these should be stored.

I have a plan in my head where I want to have a docker image, which will contain an ASP.NET Core website, MySQL database and a PHPMyAdmin frontend, and deploy this onto a droplet I have at DigitalOcean.

I've been playing around a little bit and I have a docker-compose.yml file which has the MySQL DB and PhpMyAdmin correctly linked together

version: "3"

services:
  db:
    image: mysql:latest
    container_name: mysqlDatabase
    environment:
    - MYSQL_ROOT_PASSWORD=0001
    - MYSQL_DATABASE=atestdb
    restart: always
    volumes:
    - /var/lib/mysql
  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    container_name: db-mgr
    ports:
    - "3001:80"
    environment:
    - PMA_HOST=db
    restart: always
    depends_on:
    - db

This is correctly creating a MySQL DB for me and I can connect to it with the running PHPMyAdmin front end using root / 0001 as the username/password combo.

I know I would now need to add my AspNetCore web app to this, but I'm still stumped by the best way to have my DB password.

I have looked at docker swarm/secrets, but I still don't fully understand how this works, especially if I want to check my docker-compose file into GIT/SCM. Other things I have read have suggested using environment variables, but I still don't seem to understand how that is any different to just checking in the connection string in my appsettings.json file, or for that matter, how this would work in a full CI/CD build pipeline.

This question helped my out a little getting to this point, but they still have their DB password in their docker-compose file.

It might be that I'm trying to overthink this

Any help, guidance or suggestions would be gratefully received.

Senescent answered 22/3, 2018 at 19:54 Comment(0)
M
5

If you are using Docker Swarm then you can take advantage of the secrets feature and store all your sensitive information like passwords or even the whole connection string as docker secret.

For each secret that is created Docker will mount a file inside the container. By default it will mount all the secrets in /run/secrets folder.

You can create a custom configuration provider to read the secret and map it as configuration value

public class SwarmSecretsConfigurationProvider : ConfigurationProvider
{
    private readonly IEnumerable<SwarmSecretsPath> _secretsPaths;

    public SwarmSecretsConfigurationProvider(
        IEnumerable<SwarmSecretsPath> secretsPaths)
    {
        _secretsPaths = secretsPaths;
    }

    public override void Load()
    {
        var data = new Dictionary<string, string>
            (StringComparer.OrdinalIgnoreCase);

        foreach (var secretsPath in  _secretsPaths)
        {
            if (!Directory.Exists(secretsPath.Path) && !secretsPath.Optional)
            {
                throw new FileNotFoundException(secretsPath.Path);
            }
            foreach (var filePath in Directory.GetFiles(secretsPath.Path))
            {
                var configurationKey = Path.GetFileName(filePath);
                if (secretsPath.KeyDelimiter != ":")
                {
                    configurationKey = configurationKey
                        .Replace(secretsPath.KeyDelimiter, ":");
                }
                var configurationValue = File.ReadAllText(filePath);
                data.Add(configurationKey, configurationValue);
            }
        }
        Data = data;
    }
}

then you must add the custom provider to the application configuration

public static IHostBuilder CreateHostBuilder(string[] args)
{
    return Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.AddSwarmSecrets();
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

then if you create a secret with name "my_connection_secret"

$ echo "Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;" \
| docker secret create my_connection_secret -

and map it to your service as connectionstrings:DatabaseConnection

services:
  app:
    secrets:
      - target: ConnectionStrings:DatabaseConnection
        source: my_connection_secret

it will be the same as writing it to the appsettings.config

{
  "ConnectionStrings": {
    "DatabaseConnection": "Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;"
  }
}

If you don't want to store all the connection string as secret then you can use a placeholder for the password

Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd={{pwd}};

and use another custom configuration provider to replace it with the password stored as secret.

On my blog post How to manage passwords in ASP.NET Core configuration files I explain in detail how to create a custom configuration provider that allows you to keep only the password as a secret and update the configuration string at runtime. Also the the full source code of this article is hosted on github.com/gabihodoroaga/blog-app-secrets.

Moccasin answered 15/11, 2019 at 18:38 Comment(0)
T
1

Secrets are complicated. I will say that pulling them out into environment variables kicks the problem down the road a bit, especially when you are only using docker-compose (and not something fancier like kubernetes or swarm). Your docker-compose.yaml file would look something like this:

environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}

Compose will pull MYSQL_ROOT_PASSWORD from an .env file or a command line/environment variable when you spin up your services. Most CI/CD services provide ways (either through a GUI or through some command line interface) of encrypting secrets that get mapped to environment variables on the CI server.

Not to say that environment variables are necessarily the best way of handling secrets. But if you do move to an orchestration platform, like kubernetes, there will be a straightforward path to mapping kubernetes secrets to those same environment variables.

Torso answered 22/3, 2018 at 20:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.