The secret store is available starting from Arcus Security v0.1.2. This store allows you to have a separation in configuration values and secret values in your .NET Core application. The publicly available documentation on this topic is very thorough on this topic if this is new for you.
This post is about how we can add authorization to this store. We already know that we can add multiple secret providers to the store which allows you to retrieve in a single place secrets from many sources. But we only have approached this store as a ‘pass-thru’ feature, which means that we don’t take into account who was accessing the store (for example).
In an application where you have multiple secret sources and multiple users, and you want to limit the accessibility of some users to some sources, you can’t. Or can you?
If you want to skip right to the code, all the pseudo code presented here is implemented more correctly in a POC GitHub repository.
Currently available secret store
When implementing the secret store, we tried to follow the design decisions of the already available functionality in the .NET Core landscape. This includes creating a
SecretStoreBuilder that holds the
IServiceCollection services and
IList<SecretStoreSource> secret sources. We have explicitly chosen to fully expose the secret sources in an
IList because it allows you to have full control over the secret store. This post is the proof of that.
Just to make sure we’re on the same page, here’s an example of how the secret store is added to an application and where this
SecretStoreBuilder is located.
SecretStoreSource model available in the
SecretStoreBuilder.SecretStoreSources is only a wrapper for the
ISecretProvider registration. You don’t have to worry about that too much. It’s similar to the
ServiceDescriptor in the .NET Core dependency injection container. It’s a way to centralize the way how
ISecretProvider instances are registered in the store by saving them in a canonical format.
As you already may have guessed, we’ll need something to bridge the gap between ‘authorizing users’ and ‘retrieving secrets’. I’ve chosen here for a proxy based pattern so it can stop retrieving secrets when the authorization fails. With this approach, it means that every registered secret provider in the store should take a ‘detour’ through this proxy before retrieving the secret.
Here’s an implementation of an
ISecretProvider that does just that: proxies the secret requests through our role-based authorization.
Customizing secret store
Now that we know how the authorization will be achieved (again, that’s fully up to you), we can start thinking about how we will incoperate these authorization roles into the secret store. The
SecretStoreBuilder provides two methods when adding
ISecretProvider implementations to the store:
SecretStoreBuilder.AddProvider(ISecretProvider): which will add the instance of the provider directly as a secret source to the store.
SecretStoreBuilder.AddProvider(Func<IServiceProvider, ISecretProvider>): which will use the depenency container to create an provider in a later stage.
This second method allows us to retrieve the
IRoleAuthorization instance from the container when creating our ‘proxy’
AuthorizedSecretProvider from the previous section.
The problem that remains is: how do we make sure that the registered secret providers are getting ‘wrapped’ in these authorized versions? When the secret store is being configured, and an
ISecretProvider is being added to the
SecretStoreBuilder, we don’t have any ‘hook’ where we can insert our code to wrap it in an authorized provider… Or do we?
This is the part where the customization of the secret store is fully leveraged. The
SecretStoreBuilder.SecretStoreSources is a
IList<> which means we have full control over the already registered secret providers. And also means that we are able to replace already registered secret providers. And that’s exactly what I’ve done here. The following snippet shows how I’ve created an extension that takes in a role to which the to-be-registered secret providers should be authorized to, and a function to add those secret providers in an seperate place to the secret store.
Note that we added the
AuthorizationException to the critical exceptions of the secret store, which means that when there’s an attempt to access a secret outside its permitted scope, it will handle this exception as an ‘critical exception’. Meaning that it will be placed in the end if the secret was not found in any other secret providers.
With this last piece of the puzzle, we can now on provider-level configure certain roles in which the user can access their secrets.
In this post we looked at really low-level customization and how we can manipulate the secret store into a more fine grained system that authorizes secret calls based on the role of the consumer. This is of course just scratching the surface of what you can do with the secret store. The end result of this blog post is available in a POC GitHub repository. We hope within the Arcus team that you will be using the secret store in your next project. Let us know if you have any ideas of new secret providers that can be added to the secret store.
Note that the features presented here are currently, when writing this post, only available in preview packages and not in a stable release. So, signatures, namespaces and other names are subject to change. Keep an eye on our GitHub security repository for more updates!
Thanks for reading and stay safe (both organic as technical ;))
Subscribe to our RSS feed