As a developer, dealing with application configuration and secret information is usually a love/hate relationship. We love for our applications to be flexible and dynamic, able to move from our local machines to production with ease. We hate having to keep the application configurations up to date, in-sync across developer machines and environments. I’ve found this topic is often debated, intensely discussed, even argued about among teams. I wanted to give the topic some consideration.
As part of my new role as an Architect, responsible for a new re-platforming effort, the team has been discussing how we should manage application configuration and application secrets throughout the SDLC (Software Development Life Cycle). I have my preferences for where and how to store this type of information, largely based on experience and some best practices I’ve believed over my career, but are these still the best approaches? 🤔
Goals
Before starting my evaluation I wanted to figure out what my overall goals were. I came up with these:
- Avoid hardcoded values buried deep in class or function definitions
- Enable easy migration of code between environments by updating only environment specific information
- Allow for consistent developer experience across developers and teams
- Avoid sharing of sensitive information, like credentials, among developers wherever possible
- Provide maximum amount of default or pre-defined values to reduce potential of misconfiguration errors
Evaluation
In order to meet the several goals I identified, I started my evaluation by looking at the types of data we most often need to deal with. I was able to break the types of configuration information down into three (3) different chunks of information.
Types of Information
Configuration Information
Environment Specific: ❌ Sensitive: ❌
Configuration information is the type of information that isn’t typically environmentally specific, though it can be, and isn’t sensitive. This is the type of information I don’t like to see hardcoded in the application because it is likely used in multiple places, could easily change, and ideally wouldn’t require a full build –> deployment to make a simple update. When I think of configuration information I’m thinking about default values for a function, for example, the default timeout in milliseconds for some function. The value isn’t likely to change once a happy value has been established, but it could change and can be overridden if necessary.
Example Use:
function performMagic(timeout) {
...
setTimeout(()=>{
// Do something
}, timeout || config.DEFAULT_TIMEOUT)
...
}
Recommended Handling:
Configuration information should be stored and shipped with application code. Information stored here will change infrequently and should be reviewed as part of a code review process. This is often saved in a config file or files that look something like:
config.json
{
...
"DEFAULT_TIMEOUT": 10000
...
}
Environment Information
Environment Specific: ✅ Sensitive: ❌
Environment information is the type of information that is specific to the environment, will likely change when migrating an application across environments, and is not considered sensitive. Environment information are things like URLs for resources like 3rd party APIs or databases, and client IDs (NOT client secrets!). This is information that is critical for your application to operate properly, but isn’t catastrophic if it were exposed.
Example Use:
...
const db = `${process.env.DB_HOST}:${process.env.DB_PORT}`;
...
Recommended Handling:
Environment information should be updated and stored in the environment, adjacent to the application code. Depending on the application runtime this could be as simple as a file on the file system or values read by the process running the application code. NodeJS, for example, has patterns for both of these. Environment information should not be part of the application code stored in version control, e.g. GIT, and should only be provided to the environment running the application. While not necessarily catastrophic if it were exposed, environment information should be possibly be updated and should still be handled with care.
Since we’re avoiding checking environment information into the code repository, I recommend creating an example .env file that can be used as reference for developers and checked into the code repository with other documentation.
example.env
...
DB_HOST=<DB HOST URL, Ask Tech Lead>
DB_PORT=<DB HOST PORT, Ask Tech Lead>
...
Secret Information
Environment Specific: ✅ Sensitive: ✅
Secret information is, like you might guess, secret and it should stay secret. Secret information in an application is anything that by itself or when combined with other information, potentially public information, could have serious negative consequences. This type of information is environment specific AND sensitive. This would include, for example, API keys, client secrets, database passwords, encryption/decryption keys, signing keys, etc. Secret information should be handled with the utmost care, changed regularly, and provided to the least amount of people and resources as possible.
Example Use:
const secrets = getSecrets(); // Retrieve secrets from secret manager
const dbConnection = `postgresql://${secrets.DB_USER}:${secrets.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}`;
...
Recommended Handling:
Given the sensitive nature of secret information I recommend avoiding sharing this information through plain text, email, etc. Leverage a secrets manager like HashiCorp Vault or KeePass for storage and retrieval.
I look for a secrets management solution that easily integrates with my application, offers a simple API for adding, updating, and removing secret information, with robust access control. Most cloud providers offer a secrets manager as a service.
Managing configuration and secret information can be confusing and error prone if not carefully considered. I recommend you work with your team, security professionals, and vendors to understand their recommendations before implementing a solution. This is certainly a scenario where “that’s how we’ve always done it” is no longer a responsible or smart approach.