Using HashiCorp Vault C# client with .NET Core
If your .NET application needs some secrets (e.g. database credentials), your organization might offer HashiCorp Vault to store and manage them for you. As a developer, you need a way to retrieve secrets from Vault for your application to use.
You can write your own HashiCorp Vault HTTP client to read secrets from the Vault API or use a community-maintained library.
An client library allows your C# application to retrieve secrets from Vault, depending on how your operations team manages Vault.
This tutorial demonstrates how to use a Vault C# client to retrieve static and dynamic Microsoft SQL Server database credentials from Vault. The ASP.NET Core application uses Vault Sharp, a library which provides lightweight client-side support for connecting to Vault. When database credentials change, you will need to restart the example application.
Note
This tutorial demonstrates lightweight injection of secrets into an application without additional code to handle Vault failover or application reload. For scalability, portability, and resiliency, use Vault agent instead.
Prerequisites
- Docker
- Docker Compose
- .NET v5.0.0+
tree
command line utility for visualizing directory structures.
Step 1: Retrieve the demo application
Retrieve the configuration by cloning or downloading the hashicorp-education/learn-vault-secrets-dotnet-vault repository from GitHub.
Clone the repository.
Or download the repository.
Switch your working directory to learn-vault-secrets-dotnet-vault
.
You should find the ProjectApi/
sub-directory.
The demo ASP.NET Core application leverages the VaultSharp library to communicate with Vault.
Vault and database setup
Your application, called project-api
needs to reference a Vault deployment and
Microsoft SQL Server (MSSQL). Create the dependencies by running the setup
script, which will configure and populate data for both Vault and MSSQL.
The MSSQL contains the HashiCorp
database with a table called Projects
.
It contains information about HashiCorp's projects.
Connect to the MSSQL running in the dotnet-vault_db_1
container as the user,
sa
with password, Testing!123
.
Now, select the Projects
table.
Execute the GO
command to execute the select command and view the table
entries.
Enter exit
to quit the docker exec
command.
Your operations team has given you a Vault role and secret to log into
Vault using the approle
auth method.
Note
Your Vault administrator may use a different authentication method for you get a Vault token.
Set the environment variable for VAULT_ADDR
to your Vault development instance.
To authenticate to Vault, use the role projects-api-role
and the secret ID
stored in ProjectApi/vault-agent/secret-id
. Vault will return a token and you
store it in the VAULT_TOKEN
environment variable.
Check the Vault token in the VAULT_TOKEN
environment variable. Setting
the environment variable allows you to log into Vault manually.
In the example ASP.NET Core application, you will configure the application to use the static database password. Later, you will refactor to use the dynamic database username and password.
Add a configuration provider to access Vault
In ASP.NET Core, you can add a configuration
provider
to retrieve configuration information outside of appsettings.json
. The ASP.NET
Core demo application uses a configuration provider for Vault to connect to
Vault and retrieve secrets on application startup.
Open the ProjectApi/CustomOptions/VaultConfiguration.cs
file in your preferred
text editor to view. The VaultConfigurationProvider
constructor takes a set of
options to connect to Vault using the approle
auth method.
The options in VaultOptions
include the Vault address, the role identifer,
secret, mount path for the secrets (projects-api/
), and secret type (secrets
or database
).
If you do this in your own .NET application, you will need to reconfigure the Vault configuration to authenticate with the method communicated by your operations team.
The Vault configuration provider also overrides the Load
method with a method
to retrieve the database credentials from Vault based on the SecretType
and
store it into database:userID
and database:password
configuration.
If you configure this for your own application, you can update the
GetDatabaseCredentials
with a more generic method to retrieve the secrets you
need from Vault. In the demo application, you can retrieve the static database
password from projects-api/secrets
or dynamic database username and password
from projects-api/database
.
Next, the VaultExtensions
class creates a configuration builder called
AddVault
that creates the Vault client when you build the application.
Open ProjectApi/Program.cs
. The demo application references the AddVault
configuration builder in CreateHostBuilder
. If the Vault role is defined,
the application will retrieve the Vault configuration from appsettings.json
.
However, to protect the Vault secret, it will read it from the
VAULT_SECRET_ID
environment variable.
Run the application using a static database password
The demo application uses a static database password stored in Vault's key-value store to connect to the MSSQL database. You can use the key-value store to store API tokens and other static sensitive information. You manually manage static secrets, assuming responsibility for their rotation.
Open ProjectApi/appsettings.json
with your terminal. The Vault.SecretType
defaults to secrets
, which the application uses to access that statically
defined database password.
Open ProjectApi/CustomOptions/VaultConfiguration.cs
. The configuration
provider receives the SecretType
of secrets
. If it receives that
secret type, it will retrieve the database password you discovered in Vault
at projects-api/secrets/static
.
Verify that the database password already exists at projects-api/secrets/static
with the
Vault CLI.
In this example, the operations team already added a static database password to Vault's key-value store. You may be able to add passwords or API tokens to Vault yourself, depending on whether or not your Vault administrator enables that permission.
In your terminal, run the run_app.sh
script. This will restore the .NET packages,
retrieve the Vault secret ID and set it as environment variable, and run the application.
Open a web browser and enter https://localhost:5001/api/Projects
in the
address. It returns a JSON list of HashiCorp projects, the year of
their first commit, and GitHub Links.
Enter CTRL-C
to exit the running application.
This means that the application successfully retrieved the static password for the database and connected to it. However, if your database administrator changes this static password, you will need to update it in Vault and restart your application. The pattern outlined in the demo application demonstrates the retrieval of static secrets, such as password, API tokens, or keys that must be manually rotated and updated.
Run the application using a dynamic database username and password
Your Vault administrator may provide you with dynamic database usernames and passwords instead, which allows Vault to issue a new set of credentials based on a time-to-live parameter. When a database username and password expires, you must reload the application to retrieve new credentials from Vault.
The demo application will use a dynamic database username and password managed by Vault's database secrets engine to connect to the MSSQL database. Vault can be configured with secrets engines to manage the rotation of secrets.
Open the ProjectApi/appsettings.json
in your preferred text editor, and change
the Vault.SecretType
defaults to database
instead of secrets
(at line 17).
The ProjectApi/appsettings.json
file should look as follow.
Open ProjectApi/CustomOptions/VaultConfiguration.cs
. The configuration
provider receives the SecretType
of database
. If it receives that
secret type, it will retrieve the database username and password
Vault generates at projects-api/database/creds/projects-api-role
.
The demo application accesses the Vault endpoint at
projects-api/database/creds/projects-api-role
to get a new database username
and password. Vault has been configured to generate a new username and password
that expire after two minutes. The expiration time of the secret can be updated
to the time commensurate to your security policy for a database.
To examine this, use the Vault CLI to read a new set of database credentials at
projects-api/database/creds/projects-api-role
.
In your terminal, run the run_app.sh
script. This will restore the .NET
packages, retrieve the Vault secret ID and set it as environment variable, and
run the application.
Open your browser to https://localhost:5001/api/Projects
and verify that it
returns a JSON list of HashiCorp projects, the year of their first
commit, and GitHub Links.
Wait two minutes. After a few minutes, refresh the browser page with
https://localhost:5001/api/Projects
. The API will throw a SQLException that
the application's database username can no longer log into the database.
Enter CTRL-C
to exit the running application, and then restart the application
such that the application can request a new database username and password from
Vault.
Open your browser to https://localhost:5001/api/Projects
and the API request
completes successfully again.
Re-authenticating to Vault
You can continue to reload the application to retrieve a new set of database usernames and passwords. However, you reload your application more than 5 times, your application will no longer start up and throws an error that the secret ID is invalid.
The operations team that set up Vault for this example limited the number of times you can use the secret to authenticate to Vault. To allow your application to start, you need to retrieve a new secret.
Use the terminal to run some automation to retrieve a new secret ID.
Restart the application and it will successfully authenticate to Vault.
Next steps
In this tutorial, you used the C# client library to retrieve static and dynamic secrets from HashiCorp Vault. However, the patterns shown in this example do not fully ensure the availability of secrets or account for dynamic secrets with very short lifetimes. You must build additional logic into your application to watch for changes in dynamic secrets and reload. Furthermore, your operations team may provide additional security measures to introduce a Vault token to your application securely. The automation to handle these additional measures, such as response wrapping the secret ID, will need to be added to your application code.
If you cannot add additional logic into your application code to ensure the availability of secrets, handle live reloads for rotating secrets, or additional security measures for authenticating to Vault, refer to our tutorial on using Vault Agent and Consul Template with .NET Core applications.