all Technical posts

Role-based Authorization by Low-Level Customization of the Arcus Secret Store

The secret store available in Arcus may be more customizable than you think. It allows full control of the sources where the secrets can be retrieved but it also allows you to do other things. In this post I'll show you how role-based authorization can be achieved in the secret store.

Introduction

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.

 

 

The 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.

 

Defining authorization approach

In this post I’ll by using an (too good to be true) authorization example to just give you enough food for thought but not too much that it’s overwhelming. Let’s agree that we want to add some authorization logic to this secret store that we can filter certain secret providers based on the current ‘role’ of the user.

I’ll be using three roles here: ReaderWriter and Admin purely for demonstration purposes. The purpose is that at the end of this post, I should be able to limit a user from using certain secret providers because of the current ‘role’ of the user. So, if the user is a Reader and the secret provider I want to use is only for Writers, then the secret should not be returned.

Note that in the original design of the secret store, those Readers could have accessed secrets outside their authorized scope.

Let’s define an interface that can determine based on a ‘role’ whether or not the user is authorized. This can then be implemented in any way you like, calling any external services you want.

 

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.

 

 

 

Conclusion

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 ;))
Stijn M.

 

Subscribe to our RSS feed