Welcome back to the exciting world of authorization over configurations! In a previous article, we explored the significance of securing your configurations with robust authorization. In this post, we delve into a suggested implementation of this vital aspect, using the open source project OpenFGA.
In this article:
As developers, we often encounter requirements that already have excellent existing solutions we can build upon. This approach not only saves valuable time but also ensures a reliable and scalable solution to meet our application’s demands. Let’s embark on a journey to discover the remarkable benefits of leveraging battle-tested authorization systems, designed to seamlessly integrate with a wide range of applications. Say goodbye to the complexities of building everything from scratch, and let’s embrace the power of third-party solutions to elevate our access control capabilities! ????????
Leveraging Third-Party Authorization Systems
By choosing a well-established third-party authorization system, we gain access to proven technologies, inspired by industry-leading research and development. These solutions are specifically designed to handle complex access control requirements, providing robust security and ensuring only authorized users can access sensitive configurations.
Additionally, the maturity of third-party solutions means they have been tested and optimized for optimal performance, so we can focus on building remarkable applications that prioritize user experiences and business functionalities. This allows us to allocate more time and resources to innovate and create value for our end-users.
The flexibility and adaptability of third-party solutions allow us to seamlessly integrate fine-grained authorization into our applications without reinventing the wheel. With minimal latency and optimal performance, we can rest assured that our access control layer is both powerful and user-friendly. Furthermore, these solutions often come with extensive documentation and support, enabling us to resolve any challenges swiftly.
For the purpose of this guide, we have picked OpenFGA, a CNCF backed open-source authorization system. However, remember that the market is filled with various reputable options, and you can choose the one that best aligns with your specific needs and requirements.
Defining the Authorization Model
We will now go over how to apply authorization over ConfigSets in Configu’s Orchestrator.
If we break down the basic requirements of authorization over configurations, this is what we want to achieve ultimately:
- Only users with the “read” or “write” permission can retrieve configuration values from a specified config set.
- Only users with the “write” permission can upsert configuration values from a specified config set.
- Support the config set hierarchy feature.
- Having the “read” or “write” permission on a config set gives the “read” permission on all parent sets.
- Having the “write” permission on a config set gives the “write” permission on all child sets.
Using the robust documentation OpenFGA has to offer, let’s come up with an authorization strategy that will implement the above requirements.
The authorization model defines the permission model of your system. OpenFGA recommends applying an iterative process. By applying this process, we will get the following authorization model that fulfills our basic necessities (we will tackle the hierarchy feature later in this article):
model
schema 1.1
type user
type config-set
relations
define read: [user] or write
define write: [user]
Let’s examine the authorization model:
- “user” – represents anyone who wants to access a config set.
- “config-set” – represents a config set
- “read” – represents the reading relationship between users and config sets. The “or write” gives the “write” permission the ability to also read config values”
- “write” – represents the writing relationship between users and config sets.
Checking permissions for config sets
Now we can create relationship tuples to assign permissions to users and then check if the users have them in order to authorize their access.
For the sake of our example, we will:
- Give Steve permission to write to the “production” config set.
- Give Anna permission to read from the “development” config set.
write([
{
"user":"user:steve",
"relation":"write",
"object":"config-set:production"
},
{
"user":"user:anna",
"relation":"read",
"object":"config-set:development"
}
])
Now that we assigned the permissions, for the example, we can check what Steve can do:
check(
user = "user:steve", // check if the user `user:steve`
relation = "write", // has the `write` relation
object = "config-set:production", // with the object `config-set:production`
);
Reply: true
check(
user = "user:steve", // check if the user `user:steve`
relation = "write", // has the `write` relation
object = "config-set:development", // with the object `config-set:development`
);
Reply: false
Now let’s see what Anna can do:
check(
user = "user:anna", // check if the user `user:anna`
relation = "read", // has the `write` relation
object = "config-set:development", // with the object `config-set:development`
);
Reply: true
check(
user = "user:anna", // check if the user `user:anna`
relation = "write", // has the `write` relation
object = "config-set:development", // with the object `config-set:development`
);
Reply: false
check(
user = "user:anna", // check if the user `user:anna`
relation = "read", // has the `write` relation
object = "config-set:production", // with the object `config-set:production`
);
Reply: false
We can see that Steve and Anna now have the exact permissions we expect them to according to our requirements and authorization model.
Extending the model to support config set hierarchy
To make OpenFGA aware of the hierarchical relationship between config sets we need to update the authorization model definition:
model
schema 1.1
type user
type config-set
relations
define parent: [config-set]
define child: [config-set]
define read: [user] or write or read from child
define write: [user] or write from parent
Let’s examine the changes we made to the authorization model:
- The “parent” and “child” relations were added to the “config-set”
- “parent” – Represents who the parent config set is.
- “child” – Represents the children of the config set.
- The “read” relation is now inherited from any child config set.
- The “write” relation is now inherited from any parent config set.
Like before, let’s now update the relationship tuples on top of those we made earlier:
We will:
- Create a parent-child relation between the “production” and “production/eu-west” config sets.
- Create a parent-child relation between the “development” and “development/eu-west” config sets.
- We will change Anna’s read permission from the “development” config set to the “development/eu-west” config set.
write([
{
"user":"config-set:production",
"relation":"parent",
"object":"config-set:production/eu-west"
}, {
"user":"config-set:production/eu-west",
"relation":"child",
"object":"config-set:production"
}, {
"user":"config-set:development",
"relation":"parent",
"object":"config-set:development/eu-west"
}, {
"user":"config-set:development/eu-west",
"relation":"child",
"object":"config-set:development"
},
// Delete previous permission {
"user":"user:anna",
"relation":"read",
"object":"config-set:development"
}, // create new permission {
"user":"user:anna",
"relation":"read",
"object":"config-set:development/eu-west"
}
])
Now let’s see what Steve and Anna can do:
check(
user = "user:steve", // check if the user `user:steve`
relation = "write", // has the `write` relation
object = "config-set:production", // with the object `config-set:production/eu-west`
);
Reply: true
check(
user = "user:anna", // check if the user `user:anna`
relation = "read", // has the `write` relation
object = "config-set:development", // with the object `config-set:development`
);
Reply: true
We now see that:
- Steve can write to the “production/eu-west” config set because he has the write permission on the “production” config set and the permission was cascaded downwards to the child config set.
- Anna can read from the “development” config set because she has the read permission on the “development/eu-west” config set and the permission was cascaded upwards to the parent config set.
We have successfully implemented hierarchical authorization support over config sets. ????
Taking authorization to the next level
We could take this authorization strategy even further by implementing the following changes:
- Giving users direct permission to config sets might not be what you want. If you want to implement an RBAC model you can extend the authorization to support roles and give users permissions exclusively through roles.
- You might want to give users permission for specific configs and not the entire set. You could apply direct access to configs together with parent-child relations we used earlier between configs and config sets to implement this.
- You might want to implement organizational context which will permit users access to config sets based on the organization they belong to.
Conclusion
Congratulations! You’ve now empowered your applications with the extraordinary capabilities of OpenFGA’s fine-grained authorization over your configurations. You can now confidently secure your application’s configurations with ease and flexibility. But don’t stop here – consider exploring RBAC, defining permissions for individual configurations, or even adding organizational context for more advanced access control.
Happy coding! ????✨