This article contains information about the teams agreed upon conventions and workflows regarding all things configuration in dialogporten.
The team recognize three types of configuration, which should all be handled differently:
- Normal configuration
- Autogenerated secrets
- External secrets
Table of Contents:
To aid in the discoverability of available configuration, the complete set of configuration keys, both secrets and non secrets, that is declared in the domain should be present in appsettings.Development.json
. For normal configuration, use a sensible local development default. For secrets use "TODO: Add to local secrets"
or some other invalid, yet human readable useful value. Example:
// appsettings.Development.json
{
"Infrastructure": {
// Secret that should not be exposed
"DialogDbConnectionString": "TODO: Add to local secrets",
"Altinn": {
// Non secret with a sensible value for local development
"BaseUri": "https://platform.tt02.altinn.no/"
}
}
}
By "normal configuration" we mean non secrets or configuration that that does not pose a security risk when shared externally. This type of configuration can live directly in the git repository, which gives us benefits like version controlled configuration, and a single point of truth for each environments configuration.
Each runtime defines an appsettings.{Environment}.json
for each target environment. This file should contain environment specific configuration. In addition to the specific environment configuration files there should also exist a appsettings.json
. This file should contain cross cutting environment configuration. The appsettings.{Environment}.json
configuration takes presence over the appsettings.json
configuration, meaning - if the configuration key foo
exists in both files, the value from appsettings.{Environment}.json
is used.
Due to the implicit and cross cutting nature of appsettings.json
it should be used cautiously. It's often better to let a runtime fail hard and crash due to no configuration, than using the wrong configuration value silently. Say for example that a .well-known
authentication service endpoint is configured in appsettings.json
because the same endpoint is used in Development
, Test
and Stage
. However, the developer forgets to add it to appsettings.Production.json
and thereby not overriding it for the Production
environment. Thus Production
is now using the same .well-known
endpoint as the rest of the environments and thereby open to non-production tokens. It should produce a big fat red critical error, but instead it continues on like everything is fine. Therefore the .well-known
endpoint, and many other configurations, does not belong in appsettings.json
. The absence of a configuration is often better than the wrong one silently working.
Adding, removing or updating non secret configuration is as simple as modifying appsettings.json
for cross cutting configuration or appsettings.[Environment].json
for environment specific configuration. Use caution when adding configuration to appsettings.json
, see 2.1. Word of caution. When adding a required configuration to appsettings.[Environment].json
, remember to add the configuration for all the environment specific json files. GitHub actions will do the rest when merging to main.
When a pull request containing configuration changes is merged with main, one of two things happens:
- If there are configuration changes and not changes to the
src
folder, a GitHub action workflow kicks in pushing only the configuration to Azure App Configuration and updating the sentinel value. - If there are changes to the
src
folder regardless of configuration changes, the normal build-deploy GitHub action workflow will kick in. It in turn will reference the above workflow pushing changes to Azure App Configuration and updating the sentinel value only when there are configuration changes. We can use the paths-filter to run the config action depending on if there are changes to the configuration.
Each runtime / presentation layer adds its own configuration keys to azure app configuration through appsettings.json
and appsettings.[Environment].json
with a runtime tag, indicating which runtime that owns that specific configuration value.
When developing, it is often useful to have configuration that is only used locally, eg. to override default LocalDevelopment
configuration values. If a appsettings.local.json
file is present, this file will take precedence over all other configuration files after merge. This file should not be checked in to the repository (it is in .gitignore
).
Autogenerated secrets are secrets that is generated and used by the IaC when creating resources in Azure. When a resource which require a password is generated, the IaC bootstrap script will create a new key value pair with the name of the password as its key, and the actual password as its value. The IaC then checks if the environments key vault has a secret with the same name. If it does, the autogenerated password will be discarded and the "cached" password will be used. If not, it will add/"cache" the autogenerated password to the key vault and use it when creating the resource.
An example of a autogenerated secret could be PostgreSql admin password.
The auto generated secrets will not have to be handled or known by anyone other than the specific environments azure key vault. In addition, new environments does not require human intervention to create and store these kinds of secrets.
TODO: Kan vi bruke miljøets KV direkte som cache?
Modifying autogenerated secrets is a multi step process.
To add and use the new secret:
- Generate the secret in gh action, like we do for the postgresql password:
Pass the password as parameter to the deployment step. In the case of the posgresql password, this password will be different every time the IaC runs which is why we don't want to use this value directly. The bicep module postgreSql will add
... - name: Generate postgresql password id: pwd-generator shell: pwsh run: | Import-module "./.github/tools/pwdGenerator.ps1" -Force $password = (GeneratePassword -length 30).Password echo "::add-mask::$password" echo "::set-output name=postgresqlPassword::$password" ...
postgresqlPassword
to the specific environments key vault if it does not already exist. - Use the secret by fetching it from the environment key vault
envKeyVault.getSecret('postgresqlPassword')
.
Removing or modifying autogenerated secrets is not supported through IaC.
External secrets are secrets that require human intervention to configure and must be known ahead of time, before the IaC runs. This could be a 3rd party api key or certificates.
To be able to support both external secrets and an IaC that can spin up a brand new environment on the fly, we've introduced a convention based "source key vault". The source key vault should exclusively contain external secrets and must exist before the IaC runs as it depends on it. The source key vault could be used by multiple environments.
When the IaC runs, it copies specific secrets from the source to the deployments target environment key vault based on the following naming convention:
Convention type | Source key vault | Environment key vault |
---|---|---|
Wildcard | dialogporten--any--[SecretKey] | [SecretKey] |
Environment | dialogporten--[Environment]--[SecretKey] | [SecretKey] |
There are two conventions, one that targets a specific environment, and one "any" that targets all environments that the source is connected to through IaC. The environment specific convention takes precedence over the "any" convention. For example, say we are running an IaC that targets the Stage
environment with the following keys in the source key vault:
Source key vault | Stage key vault | IsCopied | Description |
---|---|---|---|
dialogporten--any--foo | false | Environment specific "foo" convention takes precedence. | |
dialogporten--any--bar | bar | true | No environment specific "bar" exists. |
dialogporten--Stage--foo | foo | true | Overrides the "any" convention for "foo". |
dialogporten--Test--bar | false | Targets another environment. | |
dialogporten--Stage--foo--bar | foo--bar | true | Supports hierarchical values with -- as separator. |
iDoNotFollowTheConvention | false | Is ignored as it does not follow any convention. |
Instruct your friendly secrets manager to modify the desired key value pair in the source key vault before merging your pull request to the main branch.