Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Create a Azure KeyVault based configuration provider #487

Closed
muratg opened this issue Aug 12, 2016 · 19 comments
Closed

Create a Azure KeyVault based configuration provider #487

muratg opened this issue Aug 12, 2016 · 19 comments

Comments

@muratg
Copy link

muratg commented Aug 12, 2016

CoreCLR based KeyVault SDK will be ready late August per @glennc.

Let's sit down and come up with the design.

@brentschmaltz
Copy link

@muratg @glennc how is this progressing? looks interesting.

@glennc
Copy link
Member

glennc commented Aug 23, 2016

@brentschmaltz We haven't started any real work yet, just some initial discussion and design.

@cwe1ss
Copy link

cwe1ss commented Aug 23, 2016

Given two vaults:

  • myvault1-europe - https://myvault1-europe.vault.azure.net
    • Sql-ConnectionString: Server=...
    • Sql-ConnectionString-Module2: Server=...
    • ApplicationInsightsKey: 11111111-2222-3333-4444-555555555555
    • SomePassword: 123456
  • myvault2-us - https://myvault2-us.vault.azure.net
    • DbConnectionString: Server=...
    • ApplicationInsightsKey: 11111111-2222-3333-4444-555555555555
    • SomeImportantKey: SecretValue

I'd like to do the following things:

  • I want to reference the vault by alias in my code - e.g. config["myvault1"] instead config["myvault1-europe"].
    • I do NOT want to use Configuration["myvault1-europe:..."] because this name will likely be different for every environment (dev, production)
    • I could also have to change it for failover/scale out reasons.
  • I want to reference secret names by alias - e.g config["myvault1:ConnectionString"] instead config["myvault1:Sql-ConnectionString"].
    • Key Vaults may be managed by different people who have their own naming structure.
    • Secret names could contain characters which are not valid C# characters for properties and therefore break mapping to objects.
  • I want to use multiple aliases for a single secret - e.g. config["myvault1:CnnStr"] and config["myvault1:SqlConnectionString"] where both return myvault1-europe/Sql-ConnectionString
    • There could be different Options-classes with different property names - e.g. MyModule1Options.CnnStr, MyModule2Options.SqlConnectionString.
  • The secret aliases should support a way to map multiple physical secrets to Options-classes with the same property name
    • Example: I have Module1Options.ConnectionString which should point to Sql-ConnectionString and Module2Options.ConnectionString which should point to Sql-ConnectionString-Module2
  • I want to specify that all secrets from a vault should be loaded
  • I want to specify that only a given set of secrets should be loaded
    • The system shouldn't have secrets in memory which it doesn't need
  • I want to be able to use multiple vaults
  • I want to be able to decide whether both vaults are loaded into a single IConfiguration or multiple ones
  • There should be an option to throw an exception if the specified secret alias is not found in the vault
    • This should make sure typos etc. are noticed
  • I want to be able to specify the clientId and clientSecret/clientThumbprint which is required to access the vault from another IConfiguration element (to get it from environment variables or a different file)

To support most of this, we ended up building a solution that uses a two step process for loading secrets - we have a config file which defines the mappings and this is then used to load the secrets. This is how we use it:

KeyVaults.json - this defines the aliases and is used in alle environments:

{
  "KeyVaults": {
    "myvault1": {
      "Secrets": {
        "DbConnectionString": "Sql-ConnectionString",
        "Module1/SqlConnectionString": "Sql-ConnectionString",
        "Module2/SqlConnectionString": "Sql-ConnectionString-Module2",
        "ApplicationInsightsKey": "ApplicationInsightsKey"
      }
    }
  }
}

KeyVaults.Development.json - this defines the physical names of the vaults and the access credentials:

{
  "ClientID": "75e92952-701b-43f7-9f8b-8769bdd804de",
  "CertThumbprint": "a909502dd82ae41433e6f83886b00d4277a32a7b",
  "StoreLocation": "LocalMachine",
  "KeyVaults": {
    "myvault1": {
      "KeyVaultName": "myvault1-europe"
    }
  }
}

On startup, we first load this configuration with a ConfigurationBuilder and then we have a separate IKeyVaultProvider which is used to access the secrets. We also have a helper method which allows us to map stuff from this provider into an object using IServiceCollection.Configure<Options>. So, in code we can use it like this: services.Configure<Module1Options>(KeyVaultProvider, "myvault1", prefix: "Module1");

I could provide some code if you're interested - it's not really beautiful though 😄

@glennc
Copy link
Member

glennc commented Aug 23, 2016

Is the selection of europe vs us known at startup and doesn't change? or is it that some code paths want to use one and some want to use the other?

@cwe1ss
Copy link

cwe1ss commented Aug 23, 2016

Our current scenario is that we have different vaults for different environments. this means, it is known at startup and doesn't change.

If we would scale-out one service to additional data centers we might just use another environment key and create another mapping file. So this case would also be known at startup. (We don't yet have this scenario so I haven't given much thought into it)

We also don't yet regularly refresh secrets, so Key Vault outages while our application is running are no concern for us atm.

Not sure what requirements people have that use multi-tenancy etc.

@glennc
Copy link
Member

glennc commented Aug 23, 2016

Ok cool, so connecting to different key vault instances in different environments is fine then, with some code when you add it. However, the ability to ask for "ConnStr" and have that map to two different named keys in the vault isn't something that I had thought about at all.

You want this because you don't control the vaults and they end up with slightly different names. Right?

@cwe1ss
Copy link

cwe1ss commented Aug 23, 2016

exactly! let's say we have one big Key Vault for our application that contains connection strings for all modules (each uses a separate DB). In Key Vault, you would have to give them different names - e.g. "CustomersConnectionString", "SalesConnectionString", ...

In code, you might have separate libraries for each module and each might have an options class - e.g. CustomersOptions, which just contains a ConnectionString property.

If you want to use services.Configure<CustomerOptions>(...) there must be some way to map the secret "CustomersConnectionString" to the property "ConnectionString".

@cwe1ss
Copy link

cwe1ss commented Aug 23, 2016

of course, all of this might be pretty advanced - maybe having just a 1:1 mapping covers most people?! but for us, this was/is essential.

@glennc
Copy link
Member

glennc commented Aug 24, 2016

Is your app ASP.NET Core or are you dumping all the things you needed for a different app?

@cwe1ss
Copy link

cwe1ss commented Aug 24, 2016

we use one key vault per bounded context. one bounded context usually contains an ASP.NET Core based web API and there might also be background workers that process stuff from Azure Service Bus / Azure Event Hubs etc.
since we have more than 50 services, having one key vault per service seems to be too much administrative overhead. That's why we decided to see one bounded context as a security border and all services within one context are allowed to access all secrets from the vault (AFAIK you can't put permissions on a single secret in Azure Key Vault).

@bloudraak
Copy link

I'd like to separate key vaults based on the risks they pose. So you end with something like this:

  • Monitoring Vaults used by monitoring systems and acceptance tests to run synthetic transactions, this includes the credentials used for monitoring and any supporting information used to persists the results of the transactions, e.g. database connection strings etc.
  • Deployment Vaults used to store information needed to deploy systems.
  • Operational Vaults used to store certificates, SSH keys, virtual machine username and passwords
  • Application vaults used to store application specific information. Several applications share the same vault, but this really depends on whether the application deals with regulated data, e.g. healthcare, financial or PII.

As @cwe1ss mentioned, access control is per vault, not per secret, so the number of vaults used by an application depends on its business need. Vaults are often deployed to each site (westus, eastus) to ensure disaster recovery.

Secrets are uniquely named across vaults to ensure we're able to move a secret from one vault to another. The name doesn't include the location. Secrets follows a naming convention such as <bu>-<project>-<service>-<role>-<n1>-<n2>-<nn> where n1 thru nn are qualifiers. Here is an example of some secret names:

bl-sample-domain-username
bl-sample-domain-password
bl-sample-app1-web-01-username
bl-sample-app1-web-01-password
bl-sample-app1-web-01-certificate
bl-sample-app1-web-01-connectionstring
bl-sample-app1-web-01-app2-clientid
bl-sample-app1-web-01-app2-clientsecret
bl-sample-app1-web-01-app2-tenantid

The dashes are used to ease parsing.

So like environment variables, I'd rather register one or more vaults with the configuration system and optionally add a prefix (e.g. bl-sample-app1-web-01). Getting a section may be a tad more challenging, since there are restrictions on naming of secrets. But one could register a prefix e.g. bl-sample-app1-web-01-app2 and then request App2 as the section. It would simply look for values in bl-sample-app1-web-01-app2 (which is clientid, clientsecret and tenantid). All other keys are ignored.

You can also use tags to categorize secrets. For example, have a tag netfx-cfg-section which is used for the section name, another tag netfx-cfg-name so you know which name to use. There is a limit in the number of tags in a resource and be mindful that its used for numerous things (e.g. billing).

The pattern here is no different than that of ini files or environment variables, with some additional capabilities to benefit from additional metadata.

@mcm-ham
Copy link

mcm-ham commented Aug 30, 2016

One problem is azure key vault secret names cannot contain a colon yet this is what Configuration uses for sub-property names.

Maybe add support for overriding name through the use of a "ConfigKey" tag like your article uses https://azure.microsoft.com/en-us/documentation/articles/guidance-multitenant-identity-keyvault/#implementation

Another idea is automatically replace - with : in name for most common scenarios (less work for developer but maybe confusing without obvious documentation), but support a Func<SecretItem, string> configName parameter for full control over name (or even Func<SecretItem, string[]> configName for alias support as requested by cwe1ss above).

@glennc
Copy link
Member

glennc commented Aug 30, 2016

@bloudraak What you are describing sounds pretty much exactly like what we were thinking of.

@mcm-ham This is a problem with other sources as well, the environment variable one for example. IIRC we just use _ there and transparently translate : to _.

@cwe1ss
Copy link

cwe1ss commented Sep 9, 2016

sad to see this closed without any further discussion. I'm afraid the current solution will only cover the most basic requirements. Unfortunately, I don't have enough time right now to finish #502, but maybe it's useful for somebody.

@pakrym
Copy link
Contributor

pakrym commented Sep 9, 2016

@cwe1ss we are definitely going to iterate on KeyVault provider more, We just want to get simpler prerelease version out so people can actually start using it.

@moraleslos
Copy link

moraleslos commented Dec 14, 2016

I'm not sure if this is the same KeyVaultProvider as this: Microsoft.SqlServer.Management.AlwaysEncrypted.AzureKeyVaultProvider.

I need the above to work with .NET Core. The above lib is currently incompatible with NetStandard. Looks like it is dependent on the old Microsoft.Azure.KeyVault 1.0.0, which is incompatible. However the latest version of Microsoft.Azure.KeyVault is 2.0.6, which is compatible with NetStandard. Is it possible to update your provider library to use Microsoft.Azure.KeyVault 2.0.6 so that it is .NET Core compatible?

@muratg
Copy link
Author

muratg commented Dec 14, 2016

@moraleslos the provider in this repo is neither of those things -- it's an ASP.NET Core Configuration provider. Perhaps you're looking for this repo: https://github.com/Azure/azure-sdk-for-net

@pksorensen
Copy link

I had the chance to play around with this last weekend and found that most stuff was configurable and extendable for most of my use case.

You can see my secret manager here: https://gist.github.com/pksorensen/6f067c2d2e62db05e996c39254790acd#file-configuration-cs-L256

Now the thing I would like added is, that for each secret to control what is added to Data from IKeyVaultSecretManager. Right now it just add the value as a string. So you have to do IOption to get it out.

I just prefixed all keys with "KeyVault--" allowing me to do a KeyVaultOption with all secrets using configuraiton.

We have metadata stored in tags and also would liek access to attributes and content type of the secrets. So if the source called the secretmanager to actually populate Data, then we could extend it to add value as a value key, tags under a tags key ect.

And basicly you can have a model for your secret that can be resolved from configuration also and not just the value.

@divega
Copy link

divega commented Jan 4, 2017

@pksorensen I am not able to understand what you are describing, but if this is a feature request for the KeyValue provider, please create a new issue.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

10 participants