Using AppConfig Feature Flags with Organization Units
AWS AppConfig is a tool under AWS Systems Manager which handles, you guessed it, configurations of your applications. One of the major features are Feature Flags which enables you to toggle a feature wherever you need to within your solutions. It's a very nifty feature, way more capable than just a boolean toggle and we can use it in both front- and backend of our solutions.
The use-case we explore in this post is about enabling a central account (Tools) to handle feature flags for all environments and services within an Organization. The service we want to set up feature flags for in this post is called ElvaFlags Inc and is located in it's own accounts (dev and prod). This would for example enable several services to look for the same flag and can be used to enable the feature all over your application at once.
Organization Units
AWS Organizations and Organization Units are a great way to handle your multi-account strategy. Our own Tobias wrote a great post about it and also helped some during the real life scenario when we implemented this. The tl;dr you need to for this post is:
Organizations are the outer shell of the account structure. All accounts are within this.
Organization Units can be viewed as "folders" in which you can group your accounts. OUs can exist within other OUs.
The Solution
We'll start with setting up an AppConfig application in the account where we want to centrally manage the feature flags. In our case the Tools account.
Create an Application. Applications are a logical grouping of Configuration Profiles which in turn can have flags. A suggestion would be to name it after your application/solution you're developing.
Next up is creating an Environment. AWS describes them as "logical deployment groups of AppConfig targets" so a name like
dev
orprod
would make sense here.The next step is to create a Configuration Profile. Configuration profiles are either collections of feature flags or a "Freeform configuration". In this post we're focusing on FFs.
In the configuration profile you create the flags you want to toggle.
Save the configuration with the flags. Give it a version label or let AWS handle it for you.
Create an IAM Role in the same account with a policy which allows the actions:
GetLatestConfiguration
andStartConfigurationSession
. Example policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"appconfig:GetLatestConfiguration",
"appconfig:StartConfigurationSession"
],
"Resource": "*"
}
]
}
- Create a trust relationship for the role. It should allow the the action
AssumeRole
on all AWS accounts (wait for it), but have a condition. The condition is aStringLike
where you can define what OUs you want to allow access to the FFs. Example trust relationship:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "sts:AssumeRole",
"Condition": {
"ForAnyValue:StringLike": {
"aws:PrincipalOrgPaths": [
"o-orgiId/r-rootId/ou-WorkloadsId*"
]
}
}
}
]
}
In the example here I'm allowing the AssumeRole action on all OrgPaths that begin with my organization id/organization root id/WorkloadsOuId*
which essentially means all accounts within my Workloads OU. Since it's an array you can add several OUs if you need to. Now everything is ready in the Tools account!
To then fetch the feature flags in your front- or backend you need to first get the STS token and then use that as the credentials for the AppConfig client. Here is an example code snippet to finish the writeup:
const stsClient = new STSClient({
region: REGION,
});
interface Settings {
applicationID: string;
configurationProfileID: string;
environmentID: string;
}
export const getAppConfiguration = async (
settings: Settings,
) => {
const stsResponse = await stsClient.send(
new AssumeRoleCommand({
RoleArn: READ_FLAGS_ROLE,
RoleSessionName: ASSUME_ROLE_SESSION_NAME,
}),
);
if (!stsResponse.Credentials) {
return null;
}
const { AccessKeyId, SecretAccessKey, SessionToken } =
stsResponse.Credentials;
if (!AccessKeyId || !SecretAccessKey) {
throw new Error(`Failed to retrieve ${ASSUME_ROLE_SESSION_NAME} `);
}
const client = new AppConfigDataClient({
region: REGION,
credentials: {
accessKeyId: AccessKeyId,
secretAccessKey: SecretAccessKey,
sessionToken: SessionToken,
},
});
const sessionResponse = await client.send(
new StartConfigurationSessionCommand({
ApplicationIdentifier: settings.applicationID,
ConfigurationProfileIdentifier: settings.configurationProfileID,
EnvironmentIdentifier: settings.environmentID,
}),
);
const configurationResponse = await client.send(
new GetLatestConfigurationCommand({
ConfigurationToken: sessionResponse.InitialConfigurationToken,
}),
);
return JSON.parse(
Buffer.from(configurationResponse.Configuration ?? '').toString('utf-8'),
);
};
For additional reading on retrieving and using a configuration AWS has a detailed page in their docs.
That's it! Hopefully you learned something!
If you enjoyed this post you could follow me on twitter at @Paliago where I honestly mostly retweet dumb AWS related things. There might be some posts on the horizon as well πΆβπ«οΈ
Elva is a serverless-first consulting company that can help you transform or begin your AWS journey for the future