Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(jans-cedarling)!: move TOKEN_CONFIGS into the token_metadata schema #10972

Open
wants to merge 49 commits into
base: main
Choose a base branch
from

Conversation

rmarinn
Copy link
Contributor

@rmarinn rmarinn commented Mar 3, 2025

Prepare


Description

This PR moves token-to-entity mappings and token validation settings from the bootstrap properties into the policy store. This change centralizes configuration, simplifying token metadata management.

Target issue

closes #10888

Implementation Details

  • The CEDARLING_TOKEN_CONFIGS bootstrap property has been removed. The entity type name and the validation configs should now be configured via the updated token entity metadata schema (TEMS).

  • Creation of the Jans::TrustedIssuer entity has been implemented.

  • Renamed Jans::id_token to Jans::Id_token to be more consistent with Jans::Access_token and Jans::Userinfo_token.

  • Started using JSON Schema instead of examples for defining the Json Schema

  • The CEDARLING_TOKEN_CONFIGS bootstrap property has been removed. Entity type names and validation configurations should now be defined using the updated Token Entity Metadata Schema (TEMS).

  • Added support for creating the Jans::TrustedIssuer entity, which represents external identity providers (IDPs) that Cedarling can trust.

  • Renamed the Jans::id_token entity to Jans::Id_token to align better with the existing naming convention used for Jans::Access_token and Jans::Userinfo_token.

  • Replaced informal examples with JSON Schema definitions for improved validation, consistency, and documentation.

Updated Token Entity Metadata Schema

Below is the updated Token Entity Metadata Schema written in the JSON Schema specification defined at. For the full schema, see the following section.

{
    "description": "Describes how Cedarling should interpret and map JWT tokens from a specific trusted issuer.",
    "type": "object",
    "properties": {
        "trusted": {
            "description": "Indicates whether tokens from this issuer should be considered trusted by default. Defaults to true.",
            "type": "boolean",
            "default": true
        },
        "entity_type_name": {
            "description": "The Cedar entity type that tokens from this issuer should be mapped to (e.g., 'Jans::AccessToken'). This is required.",
            "type": "string"
        },
        "principal_mapping": {
            "description": "A list of Cedar principal types to which this token should be mapped (e.g., ['Jans::Workload']). Defaults to an empty list.",
            "type": "array",
            "items": {
                "type": "string"
            },
            "default": [],
            "uniqueItems": true
        },
        "token_id": {
            "description": "The claim in the token that should be treated as the unique identifier for the token. Defaults to 'jti'.",
            "type": "string",
            "default": "jti"
        },
        "user_id": {
            "description": "The primary claim to extract from the token to create the Workload entity. If not specified, Cedarling will attempt to use 'sub' before failing.",
            "type": "string"
        },
        "role_mapping": {
            "description": "The claim in the token that lists the user's roles (e.g., 'role', 'group', 'memberOf'). Defaults to 'role'.",
            "type": "string",
            "default": "role"
        },
        "workload_id": {
            "description": "The primary claim to extract from the token to create the Workload entity. If not specified, Cedarling will attempt to use 'aud', followed by 'client_id', before failing.",
            "type": "string"
        },
        "claim_mapping": {
            "description": "An object defining custom mappings from token claims to Cedar entity attributes. Defaults to an empty object.",
            "type": "object",
            "default": {}
        },
        "required_claims": {
            "description": "A list of claims that must be present in the token for it to be considered valid. Defaults to an empty list.",
            "type": "array",
            "items": {
                "type": "string"
            },
            "default": [],
            "uniqueItems": true
        }
    },
    "required": [
        "entity_type_name"
    ],
    "additionalProperties": true
}
  • added an entity_type_name field used to specify the entity type name of the token entity that will be created for the given token.
  • The principal_identifier which was used to determine the claim used as the token entity's ID has been renamed to token_id.
  • added a workload_id field used to specify which claim will be used to create the Workload entity.

Below is a non-normative example of a JSON object that adheres to the updated tokens_metadata.

    "tokens_metadata": {
        "access_token": {
            "trusted": true,
            "entity_type_name": "Jans::Access_Token",
            "token_id": "jti",
            "principal_mapping": ["Jans::Workload"],
            "workload_id" : "aud", 
            "claim_mapping": {},
            "required_claims": ["jti", "iss", "some_custom_claim"]
        },
        "id_token": {
            "entity_type_name": "Jans::Id_token",
            "token_id": "jti",
            "principal_mapping": ["Jans::User"],
            "user_id" : "sub", 
            "claim_mapping": {},
            "required_claims": ["jti", "iss", "some_custom_claim"]
        }
    },
Full Updated Policy Store Schema

This PR required significant updates to the policy store structure, which impacted a large number of tests. To ensure compatibility with existing policies and avoid introducing breaking changes, I had to review and validate many tests and pre-existing policies.

Manually verifying all these cases is both time-consuming and error-prone, increasing the risk of missing updates or inconsistencies. To address this, I propose that we adopt a formal JSON Schema definition for Cedarling’s policy store. JSON Schema provides a standard way to define, validate, and document our data structure, ensuring consistency across tests, examples, and future updates.

The official JSON Schema specification can be found at: https://json-schema.org.

For convenience, I’ve included the full JSON Schema for Cedarling’s policy store below. You can validate policy store documents using online tools such as https://www.jsonschemavalidator.net/.

This schema will also be stored in the repository at:
/jans/jans-cedarling/schema/cedarling_store_schema.json.

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "title": "Cedarling Policy Store Schema",
    "description": "Defines the structure of the policy store used by Cedarling, which contains all data necessary to verify JWT tokens and evaluate Cedar policies.",
    "type": "object",
    "properties": {
        "cedar_version": {
            "description": "The version of the Cedar language that Cedarling should use for policy evaluation. If not set, Cedarling should default to the latest supported Cedar version.",
            "type": "string"
        },
        "policy_store_version": {
            "description": "The version identifier for this policy store, used to track changes across updates.",
            "type": "string"
        },
        "policies": {
            "description": "A collection of Cedar policies and their associated metadata.",
            "$ref": "#/$defs/PolicyStore"
        },
        "policy_stores": {
            "description": "A collection of logically separated policy stores. Each store can contain its own policies, trusted issuers, and schema.",
            "$ref": "#/$defs/PolicyStore"
        }
    },
    "required": [
        "policy_store_version"
    ],
    "additionalProperties": false,
    "$defs": {
        "PolicyStore": {
            "description": "Represents a single policy store, which includes policies, trusted issuers, and the Cedar schema used for evaluation.",
            "type": "object",
            "properties": {
                "policies": {
                    "description": "A map of policy identifiers to their associated Cedar policies.",
                    "$ref": "#/$defs/CedarPolicy"
                },
                "trusted_issuers": {
                    "description": "A map of trusted issuers (by identifier) that defines which external identity providers can be trusted when evaluating authorization requests.",
                    "$ref": "#/$defs/TrustedIssuer"
                },
                "schema": {
                    "description": "The Cedar schema definition (encoded in Base64) that defines the shape of entities, actions, and context within this policy store.",
                    "type": "string"
                }
            },
            "additionalProperties": true
        },
        "CedarPolicy": {
            "description": "Represents an individual Cedar policy, including metadata and content.",
            "type": "object",
            "properties": {
                "description": {
                    "description": "A short, optional description explaining the purpose of this policy.",
                    "type": "string",
                    "default": ""
                },
                "creation_date": {
                    "description": "The date the policy was created, typically in ISO 8601 format (e.g., 2025-03-03T12:00:00Z).",
                    "type": "string"
                },
                "policy_content": {
                    "description": "The Cedar policy content, encoded as a Base64 string.",
                    "type": "string"
                }
            },
            "required": [
                "creation_date",
                "policy_content"
            ],
            "additionalProperties": true
        },
        "TrustedIssuer": {
            "description": "Represents an external identity provider (IDP) or trusted issuer, which issues tokens used during authorization evaluation.",
            "type": "object",
            "properties": {
                "name": {
                    "description": "A user-defined, human-readable identifier for this trusted issuer (e.g., 'Google', 'Azure AD').",
                    "type": "string"
                },
                "description": {
                    "description": "A short description explaining the purpose of this trusted issuer.",
                    "type": "string",
                    "default": ""
                },
                "openid_configuration_endpoint": {
                    "description": "The URL to the trusted issuer's OpenID Connect discovery document, which contains metadata about the issuer (e.g., authorization endpoint, token endpoint).",
                    "type": "string",
                    "format": "uri"
                },
                "tokens_metadata": {
                    "description": "Metadata that describes how to interpret tokens issued by this trusted issuer.",
                    "$ref": "#/$defs/TokenMetadata"
                }
            },
            "required": [
                "name",
                "openid_configuration_endpoint"
            ],
            "additionalProperties": true
        },
        "TokenMetadata": {
            "description": "Describes how Cedarling should interpret and map JWT tokens from a specific trusted issuer.",
            "type": "object",
            "properties": {
                "trusted": {
                    "description": "Indicates whether tokens from this issuer should be considered trusted by default. Defaults to true.",
                    "type": "boolean",
                    "default": true
                },
                "entity_type_name": {
                    "description": "The Cedar entity type that tokens from this issuer should be mapped to (e.g., 'Jans::AccessToken'). This is required.",
                    "type": "string"
                },
                "principal_mapping": {
                    "description": "A list of Cedar principal types to which this token should be mapped (e.g., ['Jans::Workload']). Defaults to an empty list.",
                    "type": "array",
                    "items": {
                        "type": "string"
                    },
                    "default": [],
                    "uniqueItems": true
                },
                "token_id": {
                    "description": "The claim in the token that should be treated as the unique identifier for the token. Defaults to 'jti'.",
                    "type": "string",
                    "default": "jti"
                },
                "user_id": {
                    "description": "The primary claim to extract from the token to create the Workload entity. If not specified, Cedarling will attempt to use 'sub' before failing.",
                    "type": "string"
                },
                "role_mapping": {
                    "description": "The claim in the token that lists the user's roles (e.g., 'role', 'group', 'memberOf'). Defaults to 'role'.",
                    "type": "string",
                    "default": "role"
                },
                "workload_id": {
                    "description": "The primary claim to extract from the token to create the Workload entity. If not specified, Cedarling will attempt to use 'aud', followed by 'client_id', before failing.",
                    "type": "string"
                },
                "claim_mapping": {
                    "description": "An object defining custom mappings from token claims to Cedar entity attributes. Defaults to an empty object.",
                    "type": "object",
                    "default": {}
                },
                "required_claims": {
                    "description": "A list of claims that must be present in the token for it to be considered valid. Defaults to an empty list.",
                    "type": "array",
                    "items": {
                        "type": "string"
                    },
                    "default": [],
                    "uniqueItems": true
                }
            },
            "required": [
                "entity_type_name"
            ],
            "additionalProperties": true
        }
    }
}

Test and Document the changes

  • Static code analysis has been run locally and issues have been fixed
  • Relevant unit and integration tests have been added/updated
  • Relevant documentation has been updated if any (i.e. user guides, installation and configuration guides, technical design docs etc)

Please check the below before submitting your PR. The PR will not be merged if there are no commits that start with docs: to indicate documentation changes or if the below checklist is not selected.

  • I confirm that there is no impact on the docs due to the code changes in this PR.

add required_claims, entity_type_name, entity mapping to
TokenEntityMetadata

Signed-off-by: rmarinn <[email protected]>
the following tests:

- test_failed_user_mapping
- test_failed_workload_mapping
- test_failed_access_token_mapping
- test_failed_id_token_mapping
- test_failed_userinfo_token_mapping

were removed since they are practically testing the same thing as the
following but with a more complex setup:

- can_build_entity_using_jwt
- errors_when_entity_not_in_schema

Signed-off-by: rmarinn <[email protected]>
… schema

- also implement building TrustedIssuerEntity

Signed-off-by: rmarinn <[email protected]>
mappings are already tested in the entity_builder module so there's no
need to test the same thing in mapping_entities.rs which creates a whole
cedarling instance and request just to test mappings.

Signed-off-by: rmarinn <[email protected]>
- fix policy tests the broke because of the entity builder refactor

Signed-off-by: rmarinn <[email protected]>
@rmarinn rmarinn self-assigned this Mar 3, 2025
@mo-auto mo-auto added the area-documentation Documentation needs to change as part of issue or PR label Mar 3, 2025
@mo-auto mo-auto added the kind-enhancement Issue or PR is an enhancement to an existing functionality label Mar 3, 2025
@rmarinn rmarinn requested review from nynymike and olehbozhok March 3, 2025 08:10
@rmarinn rmarinn marked this pull request as ready for review March 3, 2025 08:27
@olehbozhok olehbozhok requested a review from SafinWasi March 3, 2025 16:06
rmarinn added 2 commits March 4, 2025 14:36
- update description for user_id
- update description for workload_id

Signed-off-by: rmarinn <[email protected]>
@rmarinn
Copy link
Contributor Author

rmarinn commented Mar 4, 2025

note that we might need to move the cedarling-entities.md in the docs if #10976 merges.

- add test for building User entity without roles
- add test for building token entity without a TrustedIssuer Entity

Signed-off-by: rmarinn <[email protected]>
@@ -72,6 +128,10 @@ impl ClaimMapping {
},
}
}

pub fn apply_mapping_value(&self, value: &serde_json::Value) -> serde_json::Value {
json!(self.apply_mapping(value))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens on error case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

panics.

...but this will never happen since we already deserialized the mappings from JSON when we parsed them on startup so why would it become suddenly invalid JSON?

@@ -142,7 +110,10 @@ macro_rules! cmp_policy {

/// util function for convenient conversion Decision
pub fn get_decision(resp: &Option<cedar_policy::Response>) -> Option<cedar_policy::Decision> {
resp.as_ref().map(|v| v.decision())
resp.as_ref().map(|v| {
println!("diagnostics: {:?}\n", v.diagnostics());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to remove println

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's in the test utils.

i intentionally left that there because i couldn't figure out what was happening if the tests fails as the error messages aren't really helpful.

@rmarinn rmarinn requested a review from olehbozhok March 6, 2025 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-documentation Documentation needs to change as part of issue or PR comp-docs Touching folder /docs comp-jans-cedarling Touching folder /jans-cedarling kind-enhancement Issue or PR is an enhancement to an existing functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

refactor(jans-cedarling)!: move TOKEN_CONFIGS into the token_metadata schema
3 participants