Code in editor illustration

The initial time to a functioning working local dev environment can be a big hurdle, especially if the organization you are working for is running any significant amount of microservices.

This is the first part of a series about Azure CLI and dotnet user-secrets. You can read part 2 here:

Simplifying the process from cloning a repo to being able to run and debug the app locally with access to external resources located in the cloud can be a huge timesaver. Having a standardized way of handling this also reduces the cognitive load on developers moving between different applications.

Credentials, secrets and deployment, in general, are hard. At a customer project at Thon Hotels I worked on, all new dotnet core microservice projects that needed some kind of connection string or other secrets included a bash script secrets.sh compatible with Git bash[1], Linux and macOS.

This script creates dotnet user-secrets for the project in typically two environments (dev and test).

#!/bin/bash
# secrets.sh
ENVNAME=$(echo $1 | tr '[:upper:]' '[:lower:]')
if [ "$ENVNAME" != "dev" ] && [ "$ENVNAME" != "test" ]
then
    echo "usage: ./secrets.sh dev|test" 
    echo "example: ./secrets.sh dev"
    exit 0
fi

# The Git-bash path to Azure CLI is 'az.cmd'. Use 'az' if Unix like environment
azCmd="az.cmd" 
if [ "$(uname)" == "Darwin" ] || [ "$(expr substr $(uname -s) 1 5)" == "Linux" ] 
then
    azCmd="az"
fi

echo "Environment:  $ENVNAME"

dotnet user-secrets set mysecret "Hardcoded example"

In this example there is a lot of work for only setting one secret, but as the number of configurations required for an application to run increases, the cognitive load stays about the same for the developers cloning and running it.

Regardless of how many secrets a project contains, all you need to get up and running are three simple steps:

$ git clone https://snær-git.net/nnnn.git
$ cd nnnn/src/app
$ ./secrets.sh dev
Environment:  dev
Successfully saved mysecret = Hardcoded example to the secret store.

You probably noticed az not being used in the example. Let us start with a very simple az "whoami" equivalent:

# secrets.sh modified
echo "Environment:  $ENVNAME"
echo "Logged into:  $($azCmd account show --query name)"
echo "Logged in as: $($azCmd account show --query user.name)"

dotnet user-secrets set mysecret "Hardcoded example"

Now we'll add a secret based on a value saved in my private keyvault, but first, a gotcha when reading strings from Azure Keyvault using az, you'll end up with a "quoted" string. For some reason bash and az will helpfully add a starting and stopping " if you don't remove them. A secret containing a connection string with a hidden " or two inside of it can be very frustrating to debug.

To remove the start and end quotes you can use sed like this:

sed -e 's/^"//' -e 's/"$//'

Let's first fetch the secret value from my dev keyvault, strip away " and prepare the secret value for usage in the secrets.sh script.

# mysecret = fire

# This will have " as part of the variable value
$ MYSECRET=$($azCmd keyvault secret show --vault-name mykeyvault-$ENVNAME --name mysecret --query value)
$ echo "This is $MYSECRET!" 
This is "fire"!

# This strips "
$ MYSECRET=$($azCmd keyvault secret show --vault-name mykeyvault-$ENVNAME --name mysecret --query value | sed -e 's/^"//' -e 's/"$//')
$ echo "This is $MYSECRET!" 
This is fire!

Now we'll combine this with the secrets.sh:

# secrets.sh modified
echo "Environment:  $ENVNAME"
echo "Logged into:  $($azCmd account show --query name)"
echo "Logged in as: $($azCmd account show --query user.name)"

MYSECRET=$($azCmd keyvault secret show --vault-name mykeyvault-$ENVNAME --name mysecret --query value | sed -e 's/^"//' -e 's/"$//')
dotnet user-secrets set mysecret "Hardcoded example is $MYSECRET"

After executing ./secrets.sh dev your application now has access to mysecret and can use it as needed.

$ dotnet user-secret list
mysecret = Hardcoded example is fire

One of the strengths of using az to read values from resources in Azure like this is that the only thing a developer needs to do if one of the Keyvault values changes is to run secrets.sh for a quick refresh. You also get the ability to control who can access the secrets. Since the developers are logged in with az they can only fetch secrets and configuration values they have access to.

Another interesting fact is that by using this secrets.sh pattern you can change where the values are read from and how without having to do anything else than ensure that the developers have the correct CLI installed on their machine.

Hopefully this can inspire you into looking at solutions for automatically configuring secrets and connection strings.

If you only take one thing away from this blog post, let it be this:

Use dotnet user-secrets instead of manually adding secrets to appsettings.json juggling back and forth between "safe" values when committing to avoid secrets ending up in git history by accident 🙃


  1. Why Git bash? Simply because Git bash is installed by default when installing Git for Windows and there is minimal extra effort using $azCmd instead of az directly in the scripts. Also, since these scripts are written to be compatible with Linux as well as Git bash they work when running in WSL using the Visual Studio Code Remote - WSL extension ↩︎