Configuration, credentials, and code in cloud-native apps
Kevin Hoffman adds to the concept of configuration, a classic app development factor.
In Beyond the Twelve-Factor App, I present a new set of guidelines that builds on Heroku’s original 12 factors and reflects today’s best practices for building cloud-native applications. I have changed the order of some to indicate a deliberate sense of priority, and added factors such as telemetry, security, and the concept of “API first” that should be considerations for any application that will be running in the cloud. These new 15-factor guidelines are:
- One codebase, one application
- API first
- Dependency management
- Design, build, release, and run
- Configuration, credentials, and code
- Logs
- Disposability
- Backing services
- Environment parity
- Administrative processes
- Port binding
- Stateless processes
- Concurrency
- Telemetry
- Authentication and authorization
Factor 3 of the original 12 factors only states that you should store configuration in the environment. I believe the configuration guidance should be more explicit.
Note: Configuration chemistry
Treat configuration, credentials, and code as volatile substances that explode when combined.
That may sound a bit harsh, but failing to follow this rule will likely cause you untold frustration that will only escalate the closer you get to production with your application.
In order to be able to keep configuration separate from code and credentials, we need a very clear definition of configuration. Configuration refers to any value that can vary across deployments (e.g., developer workstation, QA, and production). This could include:
- URLs and other information about backing services, such as web services, and SMTP servers.
- Information necessary to locate and connect to databases.
- Credentials to third-party services such as Amazon AWS or APIs like Google Maps, Twitter, and Facebook.
- Information that might normally be bundled in properties files or configuration XML, or YML.
Configuration does not include internal information that is part of the application itself. Again, if the value remains the same across all deployments (it is intentionally part of your immutable build artifact), then it isn’t configuration.
Credentials are extremely sensitive information and have absolutely no business in a codebase. Oftentimes, developers will extract credentials from the compiled source code and put them in properties files or XML configuration, but this hasn’t actually solved the problem. Bundled resources, including XML and properties files, are still part of the codebase. This means credentials bundled in resource files that ship with your application are still violating this rule.
Note: Treat your apps like open source
A litmus test to see if you have properly externalized your credentials and configuration is to imagine the consequences of your application’s source code being pushed to GitHub.
If the general public were to have access to your code, have you exposed sensitive information about the resources or services on which your application relies? Can people see internal URLs, credentials to backing services, or other information that is either sensitive or irrelevant to people who don’t work in your target environments?
If you can open source your codebase without exposing sensitive or environment-specific information, then you’ve probably done a good job isolating your code, configuration, and credentials.
It should be immediately obvious why we don’t want to expose credentials, but the need for external configuration is often not as obvious. External configuration supports our ability to deploy immutable builds to multiple environments automatically via CD pipelines and helps us maintain development/production environment parity.
Externalizing configuration
It’s one thing to say that your application’s configuration should be externalized, but it’s a whole different matter to actually do it. If you’re working with a Java application, you might be bundling your release artifact with properties files. Other types of applications and languages tend to favor YAML files, while .NET applications traditionally get configuration from XML-based web.config and machine.config files.
You should consider all of these things to be anti-patterns for the cloud. All of these situations prevent you from varying configuration across environments while still maintaining your immutable release artifact.
A brute-force method for externalizing your configuration would be to get rid of all of your configuration files and then go back through your codebase and modify it to expect all of those values to be supplied by environment variables. Environment variables are considered the best practice for externalized configuration, especially on cloud platforms like Cloud Foundry or Heroku.
Depending on your cloud provider, you may be able to use its facility for managing backing services or bound services to expose structured environment variables containing service credentials and URLs to your application in a secure manner.
Another highly recommended option for externalizing configuration is to actually use a server product designed to expose configuration. One such open source server is Spring Cloud Configuration Server, but there are countless other products available. One thing you should look for when shopping for a configuration server product is support for revision control. If you are externalizing your configuration, you should be able to secure data changes as well as obtain a history of who made what changes and when. It is this requirement that makes configuration servers that sit on top of version control repositories like git so appealing.