Azure CLI and dotnet user-secrets part 2
In part 2 of my Azure CLI and dotnet user-secrets series I'll show how you can work with resources other than Azure Keyvault. The focus will primarily be on connection strings and keys the Azure CLI API exposes by default.
For a quick intro on how you can write scripts combining Azure CLI and dotnet user-secrets you can read part 1 of this series:
The Azure CLI API is built around a pretty homogenous interface and in many cases knowing how you extract data from one resource will guide the way for similar ones.
az thing verb --params uuuu
A practical example using this knowledge could be how to retrieve info about a resource group:
$ az group show --name myapp-rg
{
"id": "/subscriptions/23d771f2-8471-11ea-8e41-00155d7257aa/resourceGroups/myapp-rg",
"location": "northeurope",
"managedBy": null,
"name": "myapp-rg",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
}
Let the first example be Redis, not because the first thing you need when building an application is a distributed cache, but because it has a very straight forward way of building a connection string.
Also, we will imagine not knowing how to retrieve any type of keys or connection strings using az and let the CLI guide us.
$ az redis
az redis: error: the following arguments are required: _subcommand
usage: az redis [-h]
{create,delete,export,force-reboot,import-method,import,list,list-keys,regenerate-keys,show,update,patch-schedule,firewall-rules,server-link}
...
A good candidate immediately jumps out from the list:
$ az redis list-keys
(--resource-group --name | --ids) are required
Let's fix that:
$ az redis list-keys --resource-group myapp-rg --name redis-cache
{
"primaryKey": "aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1ETHp4cnpGQ3lPcwo=",
"secondaryKey": "aHR0cHM6Ly92aW1lby5jb20vMzYyNzYxNzgzCg=="
}
Now we only need to extract one of these keys and assign it as a user secret. I'll do it in two steps, first showing how you pick the primary key and then how you would set that as a secret.
$ az redis list-keys --resource-group myapp-rg --name redis-cache --query primaryKey
"aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1ETHp4cnpGQ3lPcwo="
As I mentioned in part 1 we have to strip the unnecessary quotes before creating the user secret. Also, we can add some error handling by piping error messages into stdout and checking for "not found" in case either the resource group or Redis cache does not exist.
Notice that we have changed over to secrets.sh
instead of working directly in the command line.
#secrets.sh
MYREDISKEY=$(az redis list-keys --resource-group myapp-rg --name redis-cache --query primaryKey 2>&1 | sed -e 's/^"//' -e 's/"$//')
if [[ $MYREDISKEY= *"not found"* ]]; then
echo "Could not get Redis key"
exit 1
fi
dotnet user-secrets set Redis:Key $MYREDISKEY
To verify my claim that the Azure CLI interface is pretty homogenous let's see if we can get the primary key for a Cosmos Db with similar code:
$ az cosmosdb list-keys --resource-group myapp-rg --name cosmos-db
This command has been deprecated and will be removed in a future release. Use 'cosmosdb keys list' instead.
{
"primaryMasterKey": "aHR0cHM6Ly90d2l0dGVyLmNvbS9wcjBuaW4vc3RhdHVzLzEyMzYwMjUwNjkxNDQ1MDYzNzIK",
"primaryReadonlyMasterKey": "aHR0cHM6Ly93d3cuaW5zdGFncmFtLmNvbS9qb2hhbmdyb25zdGFkLwo=",
"secondaryMasterKey": "aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1rb0ZHNUpodWdGcwo=",
"secondaryReadonlyMasterKey": "aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1oS1NaNTFPOUVLSQo="
}
That worked almost flawlessly with only having to change the thing and the name we used for Redis. I could pretend the deprecation warning wasn't there, instead I'll just assume the Redis command will be updated to use redis keys list
pattern at a later date.
For only retrieving the primaryMasterKey changing the property name in the query parameter to primaryMasterKey
instead of primaryKey
will work as expected.
But what if you want to connect to a SQL database, surely that will be much harder? In a way, you are correct if you authenticate with username and password. Let's assume you are and check how different picking the connection string for a SQL database is compared to keys for Redis and Cosmos Db.
$ az sql db show-connection-string --server dbserver --name mydb --client ado.net
"Server=tcp:dbserver.database.windows.net,1433;Database=mydb;User ID=<username>;Password=<password>;Encrypt=true;Connection Timeout=30;"
That's pretty close to Redis, isn't it? Instead of list-keys it's show-connection-string. That makes sense as we have changed from working with keys and are explicitly looking for a connection string.
The only complication is that we have to separately get the username and password so that we can inject them when creating the user-secret. When we created the SQL user we saved the password to an Azure Keyvault and by using what we learned in part 1 it's not hard to inject it into the connection string.
#secrets.sh
PW=$(az keyvault secret show --vault-name mykeyvault-$ENVNAME --name mysecret --query value | sed -e 's/^"//' -e 's/"$//')
# Notice double qoutes to expand PW variable https://stackoverflow.com/a/17477911
CON=$(az sql db show-connection-string --server dbserver --name mydb --client ado.net | sed -e 's/^"//' -e 's/"$//' -e 's/<username>/myappuser/' -e "s/<password>/${PW}/")
dotnet user-secrets set ConnectionStrings:mydatabase $CON
The connection string is now available for the application when debugging locally and in my mind it's obvious that most if not all things running in Azure can or will be accessible by scripts using code pretty similar to what we have looked at today.
Hopefully, you now have a better grasp of how Azure CLI can be used for automation and not only as an ad-hoc command-line tool you sometimes use instead of the Azure portal.
Bonus
This is how you can query for something not exposed by the default Azure CLI API. Instead of working against a particular thing you use resource and resource-type. If you are not sure what the resource type a particular thing is in Azure, you can either navigate to it in the portal (boo!) or run an az resource list
and identify the type
for the resource you are looking for.
In this scenario, I'll use Application Insights as the example for an Azure resource with something I need but isn't exposed by the normal API. Specifically, I'm looking for the InstrumentationKey to avoid using the same one in all of my environments.
For simplicity, let's say there is only one resource in the resource group the Application Insight that I am getting the Instrumentation key from:
$ az resource list --resource-group myapp-dev-rg
{
"id": "/subscriptions//resourceGroups/myapp-dev-rg/providers/microsoft.insights/components/appinsights",
"identity": null,
"kind": "web",
"location": "westeurope",
"managedBy": null,
"name": "appinsights",
"plan": null,
"properties": null,
"resourceGroup": "myapp-dev-rg",
"sku": null,
"tags": {},
"type": "microsoft.insights/components"
}
As you can see the type for Application Insights is microsoft.insights/components, which let's you do the query you actually want to do to get the Instrumentation key:
$ az resource show --resource-group myapp-dev-rg --name appinsights --resource-type microsoft.insights/components --query "properties.InstrumentationKey"
"f9078fbe-8495-11ea-8cbe-00155d7257aa"