Flipt supports the ability to secure its core API routes with authorization.

Flipt authorization is disabled (not required) by default.

Head to the Configuration: Authorization section to learn how to enable it.

Once enabled, all routes beneath the Management API prefix will require a policy to be evaluated before the request is allowed to proceed. The policy must evaluate to allowed == true for the request to be allowed.

  • /api/v1/

Open Policy Agent (OPA)

Open Policy Agent (OPA) is a general-purpose policy engine that can be used to configure and enforce authorization policies. OPA provides a unified toolset and framework for policy across the cloud native stack. Open Policy Agent is a CNCF project and is used by many organizations to enforce policies across their cloud-native environments.

Flipt embeds OPA to evaluate policies that determine whether a request should be allowed or denied. This means that no additional infrastructure or services are required to use OPA with Flipt.

Check out our Role-Based Access Control with Keycloak guide for an example on how to configure and use role-based access control (RBAC) with Flipt and Keycloak using OPA.

Policies

Flipt uses OPA to enforce authorization policies for the Management API. The policies are written in Rego, a powerful, declarative policy language. The path to this file is provided as described in the Configuration: Authorization section.

Part of the power of OPA is that it’s extremely flexible as it allows you to define fine-grained policies tailored to your exact needs.

Here’s an example of a simple policy that checks whether custom claims provided at authentication time include a key roles containing a value admin:

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
    claims := json.unmarshal(input.authentication.metadata["io.flipt.auth.claims"])
    "admin" in claims.roles
}

You can find more information on how to write Rego policies in the OPA documentation.

OPA has a rich set of built-in functions that can be used to write complex policies. They also provide a Rego Playground where you can test your policies before deploying them.

It’s up to you to define the policies that make sense for your organization. During policy evaluation, Flipt will pass the incoming request context to the built-in Open Policy Agent, which will then evaluate the policy against that context.

The context provided to OPA includes the following fields:

  • input.authentication: The authentication information for the request. These are specific to each authentication provider/method and can include things like the user’s roles, email, etc.
  • input.request: The incoming request details, such as the namespace, resource, and action.

Authentication Information

Flipt provides the raw authentication information to OPA for evaluation. This information is specific to the authentication method used to authenticate the request.

For example, if you’re using the OIDC authentication method, the input.authentication.metadata field may contain the user’s name and email as well as custom claims assigned to the user.

Here is an example of the input.authentication.metadata field for a request authenticated using an example OIDC provider:

{
  "io.flipt.auth.email": "user@email.com",
  "io.flipt.auth.name": "John Doe",
  "io.flipt.auth.claims": {
    "roles": ["admin", "viewer"]
  }
}

The io.flipt.auth.claims field is a JSON object that contains custom claims provided by the authentication provider. Each authentication provider may provide different claims, so it’s up to you to map these claims as needed in your policies.

The following fields are available in the input.authentication field:

  • metadata: A map of authentication metadata provided by the authentication method. This can include the user’s email, name, roles, etc.
    • io.flipt.auth.email: The user’s email address.
    • io.flipt.auth.name: The user’s name.
    • io.flipt.auth.claims: A map of all claims provided by the authentication method. This can include the user’s roles, groups, etc. These claims are marshaled into a JSON string before being passed to OPA for evaluation.
  • method: The authentication method used to authenticate the request.

Helper Functions

To make it easier to write policies, Flipt provides a set of helper functions that are available to be used for the input field.

flipt.is_auth_method(input, method)

The helper function flipt.is_auth_method(input, method) can be used to check if the request was authenticated using the specified method.

The method parameter is the authentication method name as it is registered in Flipt, e.g. oidc, token, kubernetes, github, jwt.

Example:

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
	flipt.is_auth_method(input, "jwt")
}

Mapping Identity

Each authentication method configurable within Flipt will provide different information depending on the identity. It’s up to you to combine identity information (authentication) with the requested resource (request) to make an authorization decision whether or not the request should be allowed (allow).

Some authentication methods provide user details such as roles directly, while others may provide a user ID or email that you can use to look up roles in your own system. Many authentication providers support adding custom claims to the JWT token, which can be used to provide additional information about the user.

For example, Okta allows you to add custom claims using their groups feature. An example JWT token with custom claims generated by Okta might look like this:

{
  "sub": "00uixa271s6x7qt8I0h7",
  "ver": 1,
  "iss": "https://{yourOktaDomain}",
  "aud": "0oaoiuhhch8VRtBnC0h7",
  "iat": 1574201516,
  "exp": 1574205116,
  "jti": "ID.ewMNfSvcpuqyS93OgVeCN3F2LseqROkyYjz7DNb9yhs",
  "amr": ["pwd", "mfa", "kba"],
  "idp": "00oixa26ycdNcX0VT0h7",
  "nonce": "UBGW",
  "auth_time": 1574201433,
  "groups": ["Everyone", "IT"]
}

In this example, the groups claim is used to provide the user’s organizational groups. You can then write a policy that checks for the presence of specific groups to determine whether the user should be allowed to access a particular resource.

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
    claims := json.unmarshal(input.authentication.metadata["io.flipt.auth.claims"])
    "IT" in claims.groups
}

The Rego builtin json.unmarshal function is used to convert the groups claim from a string to a JSON object that can be queried in the policy.

Flipt encodes the raw authentication claims as a JSON string to pass them to OPA for evaluation.

Further documentation on how to configure custom claims and groups in Okta can be found in the Okta Developer documentation.

Roles or groups are not a requirement for writing policies. You can write policies that check for any information provided by the authentication method, such as the user’s email, id, name, etc.

Flipt has no notion of users or roles internally, it simply passes the raw authentication information along with other request metadata to OPA for evaluation.

Request Information

The input.request field contains information about the incoming request. This includes the namespace, resource, and action of the request.

  • namespace: The namespace in Flipt of the resource being accessed. If no namespace is provided, the default namespace is used, or it is not applicable as the resource is not namespace scoped (e.g. authentication)

  • resource: The resource being accessed. This can be one of:

    • namespace: Access to namespace resources (e.g., listing or creating namespaces).
    • flag: Access to flag resources and sub-resources (e.g., listing or creating flags, variants, rules or rollouts).
    • segment: Access to segment resources and sub-resources (e.g., listing or creating segments, constraints or distributions).
    • authentication: Access to authentication resources (e.g., listing or creating client tokens).
  • subject: The (optional) nested subject of the request. This can be one of:

    • namespace: Access to namespace resources.
    • flag: Access to flag resources.
      • variant: Access to flag variant resources.
      • rule: Access to flag rule resources.
      • rollout: Access to flag rollout resources.
    • segment: Access to segment resources.
    • token: Access to client token resources.
  • action: The action being performed on the resource. This can be one of:

    • create: Access to create resources.
    • read: Access to list or read resources.
    • update: Access to update resources.
    • delete: Access to delete resources.

Here’s an example of the input.request field for a request to list flags in the default namespace:

{
  "namespace": "default",
  "resource": "flag",
  "subject": "flag",
  "action": "read"
}

Here is an example policy that allows a user to list flags in the default namespace:

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
    input.request.namespace == "default"
    input.request.resource == "flag"
    input.request.action == "read"
}

Combining the above policy with the user information policy from the previous example, you can create a policy that allows users with the IT group to delete flags in the default namespace:

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
    claims := json.unmarshal(input.authentication.metadata["io.flipt.auth.claims"])
    "IT" in claims.groups
    input.request.namespace == "default"
    input.request.resource == "flag"
    input.request.action == "delete"
}

External Data

OPA policies can also use external data sources to make decisions. This can be useful when you need to make decisions based on data that is not available in the request context.

For example, if your authentication method does not provide user roles, you could use an external data source to map user IDs to roles.

Here is an example policy that uses an external data source to check if the user has the admin role:

policy.rego
package flipt.authz.v1

import rego.v1

default allow := false

allow if {
    role := data.roles[input.authentication.metadata["io.flipt.auth.name"]]
    role == "admin"
}

And here is an example of the external data source that maps user IDs to roles:

data.json
{
  "roles": {
    "user1": "admin",
    "user2": "viewer"
  }
}

Flipt allows you to define external data sources in the configuration file. You can find more information on how to configure external data sources in the Configuration: Authorization section.

The combination of OPA’s flexible policy language and the ability to use external data sources makes it possible to define complex authorization policies that can adapt to your organization’s needs.

Authoring Policies

While the examples provided in this document are simple, you can write policies that are as complex as you need. OPA provides a rich set of built-in functions that can be used to write complex policies.

Check out our Role-Based Access Control with Keycloak guide for an example on how to configure and use role-based access control (RBAC) with Flipt and Keycloak using OPA.

Learning how to write policies in Rego can be challenging at first, but OPA provides extensive documentation on the Rego Language as well as a Rego Playground where you can test your policies before deploying them.

OPA also provides a testing framework that you can use to write unit tests for your policies. This can help ensure that your policies are working as expected before deploying them to production.

Here are some resources to help you get started with writing and testing policies:

If you have any questions or need help writing policies for Flipt, feel free to reach out to us in our Discord community.