Skip to content

This project is at the proof-of-concept stage. The end goal is to manage secrets stored in Azure Key Vault in the most secure way possible.

Notifications You must be signed in to change notification settings

dowdian/Dowdian.DNN.VaultApi

Repository files navigation

DNN Azure Key Vault Web API

Introduction

Please Note: This solution is "working" in the sense that it does what it's supposed to do. However, it is not yet complete. The documentation is incomplete and the solution is not yet fully tested.

These instructions are accurate as of May 30, 2024. If you're reading this in the future (which I presume you are), you may need to adjust the instructions to match the current state of the Azure portal. This is what made creating this project so damn hard. All the instructions, tutorials and sample projects I could find were convoluted and/or out of date. This goal of this project is to create a simple project that people (i.e: me) can learn from.

This document is boken up into six sections:

  1. Certificate Creation You'll need to this no matter how you want to use this solution.
  2. Azure Key Vault Setup Also required no matter which path you choose, this is for setting up the Azure Key Vault and the App Registration in Azure Entra (formerly Active Directory).
  3. Installing the API locally This is for using the API in a development environment on your local machine or on a provisioned server.
  4. Installing the API in Azure This is for using the API in an Azure App Service instance.
  5. Setting up the development environment This is for stepping through or modifying the code, or (if you're feeling very generous) contributing to the project. πŸ™
  6. Using the API Once you have the API set up wherever it is, this is how you can use it in your own projects to manage secrets either locally in a web.config or in the Azure Key Vault.

This project is at the proof of concept stage. The end goal is to easily manage secrets stored in DNN in the most secure ways possible. There may well be some obvious gaps. Please point them out with kindness.

This solution:

  • Uses a self-signed 4096 bit certificate to authenticate to Azure Key Vault.
  • Uses a seperate self-signed 4096 bit certificate to encrypt sections of the web.config files (two web.config files to be exact).
  • Includes basic CRUD methods for managing the Secrets implemented as Web API methods available to authenticated processes within a DNN instance.
  • Does not modify the schema of the DNN database.
  • Can be used either in a development environemnt (localhost), in a self-hosted server situation, or in an Azure App Service instance. These instructions focus on a localhost setup (for now).

Certificates Creation

You'll need to create two self-signed certificates. One to authenticate to the Key Vault and the second to encrypt and decrypt sections of the web.config.

In the context of certificates, there are generally two types of keys: signature keys and exchange keys.

  • Signature keys are used to create digital signatures for authenticity and integrity. They are used to verify that data hasn't been tampered with and that it originates from the specified source.
  • Exchange keys, on the other hand, are used for encrypting and decrypting the symmetric key in secure communications. In a typical secure communication, the symmetric key is used to encrypt the actual message data, and then the symmetric key itself is encrypted with the recipient's public exchange key. The recipient can then use their private exchange key to decrypt the symmetric key, and then use that to decrypt the actual message.

Let's start things off with the signature key certificate.

  1. Create a new folder to store your certificates and their related files.

  2. Open a PowerShell window as an administrator and navigate to your new folder. (Keep it open when you're done, you'll need it again later.)

  3. Run the following command to create a self-signed signature key certificate in your current directory. This will cause Windows to ask you to enter a password three times across two dialogs. Leaving it blank is not an option:

    makecert –r -n "cn=DnnVaultApiSignature" DnnVaultApiSignature.cer -sv DnnVaultApiSignature.pvk -b 05/30/2024 -e 06/30/2024 -len 4096
    • -r creates a self-signed certificate.
    • -n specifies the subject name of the certificate.
    • -b specifies the start date of the certificate.
    • -e specifies the end date of the certificate.
    • -len specifies the length of the key.
    • DnnVaultApiSignature.cer -sv DnnVaultApiSignature.pvk specifies the output file names for the certificate and the private key file.

    Note: When I copied and pasted this command into the command prompt, it didn't work. I had to type it out manually. I'm not sure why. πŸ€·β€β™‚οΈ

  4. Run the following command to get the thumbprint of the certificate:

    Get-PfxCertificate -FilePath .\DnnVaultApiSignature.cer | fl Thumbprint | Out-File -FilePath .\DnnVaultApiSignatureThumbprint.txt
  5. Run the following command to extract the combined certificate and private key into a new Personal Information Exchange (PFX) file:

    pvk2pfx -pvk DnnVaultApiSignature.pvk -spc DnnVaultApiSignature.cer -pfx DnnVaultApiSignature.pfx -pi <password>

    Replace <password> with the password you chose when creating the certificate.

Now you should have four files in your new certificates folder. Next up, let's create the exchange key certificate. It's pretty much a line-for-line repeat of the signature key process with just a few small differences.

  1. Run the following command to create a self-signed exchange key certificate in your current directory. I would use the same password as last time, unless you're feeling extra spicy:

    makecert -r -n "cn=DnnVaultApiExchange" -sky exchange DnnVaultApiExchange.cer -sv DnnVaultApiExchange.pvk -b 05/18/2024 -e 06/18/2024 -len 4096
    • -sky exchange specifies the subject's key type. Here, exchange means it's an exchange key.
  2. Run the following command to get the thumbprint of the certificate:

    Get-PfxCertificate -FilePath .\DnnVaultApiExchange.cer | fl Thumbprint | Out-File -FilePath .\DnnVaultApiExchangeThumbprint.txt
  3. Run the following command to create the PFX file:

    pvk2pfx -pvk DnnVaultApiExchange.pvk -spc DnnVaultApiExchange.cer -pfx DnnVaultApiExchange.pfx -pi <password>

    Replace <password> with the password you chose when creating this certificate.

Now you should have eight files in your certificates folder and this should be all you need to install the API locally, install in Azure, or to set up the development environment. I bet you feel more secure already! πŸŽ‰

Azure Key Vault Setup

  1. If you don't already have an Azure account, you can create one for free here.
  2. You will also need to create an App Registration in Azure Active Directory to uniquely identify your DNN instance. This will be used to authenticate to the Key Vault. You can follow the instructions here.
    1. Be certain to add a redirect URI (details).

      Redirect URI

    2. Upload the public key of the Signature Certificate to the App registration in Azure Active Directory (details).

      App Registration Certificate

    Make note of the App Registration's Application (client) ID and the Directory (tenant) ID as you will need these later.

  3. If you don't already have a Azure Key Vault associated with you Azure account, you can create one by following the instructions here.

    make note of the URI of the Key Vault as you will need this later.

  4. Give the App registration the necessary permissions to access the Key Vault. You can follow the instructions here.

Installing the API locally

I'll write this section up after I add the automatic build and install scripts. For now, you can follow the instructions in the Development Environment Setup section to get the API up and running on your local machine.

Installing the API in Azure

Here again, I'll write this section up after I add the automatic build and install scripts. For now, you can follow the instructions in the Development Environment Setup section to get the API up and running on your local machine.

Development Environment Setup

This solution was built using the Upendo DNN Generator. If you're already familiar you'll have a bit of a head start, but if you're not don't worry. We'll walk through the set up from the beginning.

One more note before we get started, In order for the IIS process (or an Azure App Service for that matter) to have access to the certificates, the certificates have to be associated with a user. You can create your own local user if you like with just exactly the right premissions for your solution, or just use NETWORK SERVICE, which has a very nice mix of enough permissions to do what we need to do and easy-peasy. In this I choose the easy way.

File system Setup

To begin, let's get DNN and the project directory structure set up.

  1. Clone this repository to a directory close to the root of your local machine (to avoid any potential issues with long paths).

  2. Create a new folder in the root of the solution called Website.

  3. Give the NETWORK SERVICE account full permissions on the Website directory to allow IIS to read and write to the directory.

    Folder permissions

  4. Unzip a fresh copy of DNN into the new Website directory.

Database setup

  1. Create (or overwrite!) a database in your local SQL Server instance and allow IIS to attach to it using the NETWORK SERVICE account using the following SQL script:

    USE [master]
    GO
    
    DECLARE @DatabaseName AS sysname = 'YourDatabaseName'
    DECLARE @PathToFiles AS sysname = 'C:\Path\To\Your\Database\Files'
    DECLARE @Sql as nvarchar(max)
    
    -- If I'm running this script and the database already exists, it's 
    -- because I screwed up my development environment and I need to start over.
    -- If I delete something I shouldn't have, well, that's on me.
    IF EXISTS (select * from sys.databases where name = @DatabaseName)
    BEGIN
        -- Kick everyone out.
        SET @Sql = 'ALTER DATABASE ' + @DatabaseName + ' SET  SINGLE_USER WITH ROLLBACK IMMEDIATE'
        EXEC (@Sql)
    
        -- Remove any backup history that may have accumulated
        EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = @DatabaseName
    
        -- Delete the database an everything in it.
        SET @Sql = 'DROP DATABASE ' + @DatabaseName
        EXEC (@Sql)
    END
    
    -- Now, with a truely clean slate, create a new database.
    SET @Sql = 'CREATE DATABASE ' + @DatabaseName + ' CONTAINMENT = NONE 
        ON  PRIMARY ( NAME = N''' + @DatabaseName + ''', FILENAME = N''' + @PathToFiles + '\' + @DatabaseName + '.mdf'', SIZE = 8192KB , FILEGROWTH = 65536KB ) 
        LOG ON ( NAME = N''' + @DatabaseName + '_log'', FILENAME = N''' + @PathToFiles + '\' + @DatabaseName + '_log.ldf'', SIZE = 8192KB , FILEGROWTH = 65536KB ) 
        WITH LEDGER = OFF'
    EXEC (@Sql)
    
    -- Lastly, let's give full permissions to NETWORK SERVICE to access our new database.
    SET @Sql = 'USE ' + @DatabaseName + ' 
        CREATE USER [NT AUTHORITY\NETWORK SERVICE]
        ALTER AUTHORIZATION ON SCHEMA::[db_owner] TO [NT AUTHORITY\NETWORK SERVICE]
        ALTER ROLE [db_owner] ADD MEMBER [NT AUTHORITY\NETWORK SERVICE]'
    EXEC (@Sql)
    GO

    Replace YourDatabaseName and Path\To\Your\Database\Files with appropriate values.

    This should result in a lovely new database, seen here in SSMS, all set up for IIS to use it, which is good, because IIS is next.

    Database permissions

IIS Setup

  1. Open IIS Manager and add an Application under the Default Web Site with the following settings:

    • Alias: DnnVaultApi
    • Physical Path: C:\Path\To\Your\Project\Directory\Website

      Replace PathToYourProjectDirectory with the appropriate value.

    • Application Pool: DefaultAppPool

      Note: If you already have applications running under your DefaultAppPool, you may want to create a new AppPool for this application.

  2. Modify the DefaultAppPool (or the AppPool of your choice) to use the NETWORK SERVICE account as its identity.

    IIS Application Pool Permissions

Certificates Setup

  1. Run the following commands in an Administrator PowerShell console to install the certificates on your local machine:

    certutil -f -p <password> -importpfx .\DnnVaultApiExchange.pfx
    certutil -f -p <password> -importpfx .\DnnVaultApiSignature.pfx

    Replace <password> with the password(s) you chose when creating the certificates.

  2. Lastly, you'll need to give the NETWORK SERVICE account full permissions on the certificates so that our IIS process will have access to them.

  3. Open the Certificates MMC snap-in by running certlm.msc in the Run dialog.

  4. Navigate to Personal > Certificates.

  5. Right-click on the DnnVaultApiSignature certificate and select All Tasks > Manage Private Keys.

  6. Add the NETWORK SERVICE account with Full Control permissions.

  7. Right-click on the DnnVaultApiExchange certificate and select All Tasks > Manage Private Keys.

  8. Add the NETWORK SERVICE account with Full Control permissions.

    Exchange Key Certificate permissions Signature Key Certificate permissions

Seriously, I can't believe you're still with me!!! Hang on! We're almost there.

Running the new environment

  1. Using your favorite browser, navigate to http://localhost/DnnVaultApi to complete the DNN installation process.

    • Give the host user a password.
    • Database Setup: Custom
    • Database Type: SQL Server/SQL Server Express Database
    • Server Name: (local)
    • Database Name: YourDatabaseName
    • Security: Integrated
    • All other values can be left as their default values. This should result in a perfectly adequate DNN instance.

    Perfectly Adequate DNN

  2. Now, with DNN installed, we can move over to building and installing the DnnVaultApi. Open the solution in Visual Studio and build it in Release mode. This will create the installer files in the .\Website\Install\Modules directory.

    How to build a release package

  3. Install the DnnVaultApi module in the DNN instance.

    Module Install Step 1

    Module Install Step 2

    Module Install Step 3

You made it!!! We're all done! Now you should be all set to use the API to Create, Read, Update, and Delete secrets both locally in a web.config file in your Azure Key Vault. πŸŽ‰

If you're ready to jump right into the code, rebuild the application in debug mode and attach your debugger to IIS. You can set breakpoints in the code and see how it works. (...or doesn't work. Results may vary.)

Using the API

  1. First, you'll need to make one more change to your web.config file. Add the following app settings to the <appSettings> section:
      <add key="TenantId" value="[Directory (tenant) ID]" />
      <add key="ClientApplicationId" value="[Your Application (client) ID]" />
      <add key="Thumbprint" value="[Your Certificate Thumbprint]" />
      <add key="KeyVaultUri" value="[Your Vault URI]" />

    Replace [Directory (tenant) ID], [Your Application (client) ID], [Your Certificate Thumbprint], and [Your Vault URI] with the values you collected earlier. This step will change in the near future as I intend to encrypt one or more sections of the web.config to hide these values properly.

  2. Using your favorite browser, log into your DNN instance.
  3. Open Dev Tools and navigate to the Console tab.
  4. Run the following JavaScript code to test the API by creating a new secret named "DnnVaultApiTestValue" with a value of "SuperSecretValueHandleWithCare":
    const url = '/DnnVaultApi/API/DnnVaultApi/DnnVaultApi/CreateSecret?secretName=DnnVaultApiTestValue&secretValue=SuperSecretValueHandleWithCare';
    const options = {
        method: 'GET',
        headers: {
            beforeSend: $.ServicesFramework(0)
        }
    };
    new Promise((resolve, reject) => {
        fetch(url, options)
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    console.log('Damn. Something went wrong on the server.');
                    console.log(response);
                    reject(response);
                }
            })
            .then(json => {
                console.log(json);
                resolve(json);
            })
            .catch(error => {
                cosole.log('Damn. Something went wrong on the client.');
                console.log(error);
                reject(error);
            });
    });
    This should return a message indicating that the secret was created successfully.
    A new secret with the name DnnVaultApiTestValue has been successfully created in the Vault.
    
  5. Run the following JavaScript code to test the API by retrieving the value of the secret you just created (notice that the only thing changed is the URL):
    const url = '/DnnVaultApi/API/DnnVaultApi/DnnVaultApi/GetSecret?secretName=DnnVaultApiTestValue';
    const options = {
        method: 'GET',
        headers: {
            beforeSend: $.ServicesFramework(0)
        }
    };
    new Promise((resolve, reject) => {
        fetch(url, options)
            .then(response => {
                if (response.ok) {
                    return response.json();
                } else {
                    console.log('Damn. Something went wrong on the server.');
                    console.log(response);
                    reject(response);
                }
            })
            .then(json => {
                console.log(json);
                resolve(json);
            })
            .catch(error => {
                cosole.log('Damn. Something went wrong on the client.');
                console.log(error);
                reject(error);
            });
    });

You should see the following output in the console:

SuperSecretValueHandleWithCare

Next Steps:

  1. Build the UI in the Persona Bar for entering the [Directory (tenant) ID], [Your Application (client) ID], [Your Certificate Thumbprint], and [Your Vault URI] values to be saved in the encrypted section of the web.config.
  2. Build support for Bitwarden Secrets Manager

Conclusion

I have been told that the best way to ask for help online is to make a statement with absolute certainty and wait for the good folks of the world to tell you how wrong you are. With this in mind, I can say with the utmost conviction that the above instructions will work perfectly for you. If they don't, please let me know so that I can correct them. πŸ™

Acknowledgements

I would like to thank the creators of the following resources for their help in creating this project:

  1. https://kamranicus.com/azure-key-vault-config-encryption-azure/
  2. https://github.com/HoeflingSoftware/Dnn.KeyMaster
  3. https://intelequia.com/en/blog/post/storing-azure-app-service-secrets-on-azure-key-vault
  4. https://stackoverflow.com/questions/52044838/how-to-use-certificate-from-azure-keyvault-with-httpclient-without-extracting-it
  5. https://www.c-sharpcorner.com/article/accessing-azure-key-vaults-using-certification/
  6. https://stackoverflow.com/questions/67646500/azure-api-authenticating-apis-with-a-client-certificate-oauth-2-0
  7. Upendo Ventures for the Upendo DNN Generator
  8. The DNN Community for the DNN Platform

About

This project is at the proof-of-concept stage. The end goal is to manage secrets stored in Azure Key Vault in the most secure way possible.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published