I was talking with a colleague on switching from Vault to Kubernetes secrets the other day. To my surprise a lot of our application code was dependent on our Vault implementation. Our migration would need opening a PR for every service we maintain. While trying hard not to grimace I started wondering on ways how to avoid this from the start. Two alternatives came to my mind which I'll describe here.
An alternative is to wrap the logic of dealing with Vault's specifics and bundle that into a library. As result, once migrating to K8s secrets we could change the library's code once and it will impact the services using it. But it wouldn't be a cross-language solution i.e. you would have to maintain one library for Go and another library for Node.js.
Another solution is to inject the secrets into the application process as environment variables. This approach decouples your applications from your secret management approach: the changes happen once for all your applications. Since it allows for more flexibility and portability than the solution A, this solution would be a better choice for a long-term scenario. This solution also follows the 12 Factor App approach for configuring applications.
Handling secrets for applications is a complex subject. Even if you use a solution that facilitates the concerns I covered here - being language-agnostic and having it configured as part of the provisioning of your images/instances - you're not exempt of application logic logging secrets in the wild.
You will spend more time thinking, designing and implementing decoupled components. It will also make your architecture easier to maintain and easier to apply changes.
If you want to read on secrets management in detail I've selected a few resources as follow up:
- Secrets management guide
- Managing Passwords and Application Secrets: Common Anti-Patterns
- Secret Management Architectures: Finding the balance between security and complexity