Skip to content

Commit

Permalink
feat(jans-cedarling): update policy_store.json parser and schema (#9910)
Browse files Browse the repository at this point in the history
* feat(jans-cedarling): add trusted_issuers field to the PolicyStore

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

* docs: changes in policy store docs

Signed-off-by: Arnab Dutta <[email protected]>

* feat(jans-cedarling): implement Deserialize for TokenKind

- implement Deserialize for TokenKind instead of using the
  derialize_with macro

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

* refactor(jans-cedarling): move test mod from init/test.rs into init/policy_store.rs

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

* test(jans-cedarling): remove redundant assert in errors_on_multiple_mappings

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

* feat(jans-cedarling): implement Copy trait for TokenKind enum

- added the Copy trait implementation to TokenKind for more efficient value handling

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

* refactor(jans-cedarling): remove unnecessary .clone() calls on TokenKind

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

* refactor(jans-cedarling): change MultipleRoleMappings error to use Vec<String>

- updated the MultipleRoleMappings error variant to store a Vec<String>
  instead of a single String, allowing it to capture multiple tokens with role mappings.

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

* refactor(jans-cedarling): rename fields in PolicyStore to be more descriptive

- rename `schema` field in `PolicyStore` to `cedar_schema`
- rename `policies` field in `PolicyStore` to `cedar_policies`

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

* refactor(jans-cedarling): improve naming and deserialization for PolicyStore

- policy_store_id is now only required when loading from Lock Master, simplifying the structure of policy_store.json
- renamed and simplified field and function names for better clarity in policy deserialization
- updated docstrings to enhance understanding of PolicyStore fields and deserialization process
- updated test cases to reflect new naming conventions and improve error handling

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

* feat(jans-cedarling): introduce cedar_version field in policy_store.json

- added support for the cedar_version field to specify the version of Cedar being used.
- this enhancement allows for version-specific parsing of schemas and policies during deserialization.
- updated relevant structures and deserialization logic to validate the cedar_version format.

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

* refactor(jans-cedarling): move deserialization logic for multiple roles to PolicyStore

- checking for multiple roles now occurs during the deserialization of PolicyStore
- the corresponding test has been relocated from `init/policy_store.rs` to
  `common/policy_store.rs` for better organization and clarity.

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

* refactor(jans-cedarling): rename parse_policy to parse_single_policy

- rename `parse_policy` to `parse_single_policy` to make the intent of
  calling the function clearer

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

* docs: fixing review comments

Signed-off-by: Arnab Dutta <[email protected]>

* docs(jans-cedarling): add missing docstrings in common/policy_store.rs

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

* docs(jans-cedarling): update docs/cedarling/cedarling-policy-store.md

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

* docs(jans-cedarling): update docs/dedarling/cedarling-jwt.md

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

* docs: fixing review comments

Signed-off-by: Arnab Dutta <[email protected]>

* fix(jans-cedarling): uncomment previously commented functions

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

* docs: correct policy store format

Signed-off-by: Arnab Dutta <[email protected]>

* docs: correct policy store format

Signed-off-by: Arnab Dutta <[email protected]>

* docs: correct policy store format

Signed-off-by: Arnab Dutta <[email protected]>

* fix(jans-cedarling): remove unused commented code

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

* docs(jans-cedarling): fix docstrings in PolicyStore

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

* chore(jans-cedarling): renamed `check_token_metadata` to `parse_and_check_token_metadata`

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

* refactor(jans-cedarling): replace custom version parsing with the semver crate

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

* refactor(jans-cedarling): simplify TokenKind parsing

- removed the need for a Visitor in parsing logic
- users now pass `access_token`, `id_token`, `userinfo_token`,
  or `transaction_token` (case-insensitive) as the token type

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

* chore(jans-cedarling): simplify policy parsing by removing unnecessary Ok wrapper

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

* test(jans-cedarling): add unit test for handling invalid token type

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

* chore(jans-cedarling): replace string with JSON macro for invalid token metadata test

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

* feat(jans-cedarling): enhance policy deserialization error handling

- updated the deserialization logic to collect and report multiple errors encountered during policy parsing

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

* test(jans-cedarling): move tests to a separate file and enhance input clarity

- reorganized tests into a dedicated file for better structure
- improved readability of policy and schema inputs in the tests

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

* chore(jans-cedarling): resolve Clippy warnings

- fixed needless borrows to improve code efficiency

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

* test(jans-cedarling): add specific error assertion in unit tests

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

* chore(jans-cedarling): replace `person_id` with `user_id`

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

* docs(jans-cedarling): fix incorrect example

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

---------

Signed-off-by: rmarinn <[email protected]>
Signed-off-by: Arnab Dutta <[email protected]>
Co-authored-by: Arnab Dutta <[email protected]>
  • Loading branch information
rmarinn and duttarnab authored Oct 28, 2024
1 parent f35db99 commit 98f3d5b
Show file tree
Hide file tree
Showing 25 changed files with 991 additions and 503 deletions.
71 changes: 17 additions & 54 deletions docs/cedarling/cedarling-jwt.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,67 +12,30 @@ tags:

![](../assets/lock-cedarling-diagram-4.jpg)

### Trusted Issuer Schema
# Json Web Token Validation

* **`name`** : String, no spaces
* **`description`** : String
* **`openid_configuration_endpoint`** : String with `https` url of `.well-known` for domain.
* **`access_tokens`** : Object with claims:
* `trusted`: `True | False`
* **`id_tokens`** : Object with claims:
* `trusted`: `True | False`
* `principal_identifier`: the token claim used to identify the User entity (in SAML jargon it's
the "NameID format"). This claim is optional--it may be present in the Userinfo token. Defaults to `sub`.
* `role_mapping`: A list of the User's roles
* **`userinfo_tokens`** :
* `trusted`: `True | False`
* `principal_identifier`: the token claim used to identify the User entity (in SAML jargon it's
the "NameID format"). This claim is optional--it may be present in the Userinfo token. Defaults to `sub`.
* `role_mapping`: A list of the User's roles
* **`tx_tokens`** :
* `trusted`: `True | False`
**Note:** To enable Json Web Token (JWT) Validation in Cedarling, it is required to populate the [`trusted_issuers`](./cedarling-policy-store.md#trusted-issuer-schema) field in the [Policy Store](./cedarling-policy-store.md).

Non-normative example:
```
[
{"name": "IDP-1",
"description": "Acme IDP",
"openid_configuration_endpoint": "https://acme.com/.well-known/openid-configuration",
"access_tokens": {"trusted": True},
"id_tokens": {"trusted": True, "principal_identifier": "email"},
"userinfo_tokens": {"trusted": True},
"tx_tokens": {"trusted": True}
},
{IDP-2},
{IDP-3}...
]
```
### Enabling JWT Signature Validation

### JWT Validation
Cedarling can validate JWT signatures for enhanced security. To enable this feature, set the `CEDARLING_JWT_VALIDATION` bootstrap property to `True`. For development and testing purposes, you can set this property to `False` and submit an unsigned JWT, such as one generated from [JWT.io](https://jwt.io).

Optionally, the Cedarling can validate the signatures of the JWTs for developers. To enable this,
set the `CEDARLING_JWT_VALIDATION` bootstrap property to `True`. For testing, developers can set
this property to `False` and submit an unsigned JWT, for example a JWT generated on
[JWT.io](https://jwt.io).
### Public Key Management

If token validation is enabled, on initiatilization the Cedarling downloads the public keys of
the Trusted IDPs specified in the Cedarling policy store. The Cedarling uses the JWT `iss`
claim to determine the right keys for validation.
When token validation is enabled, Cedarling downloads the public keys of the Trusted IDPs specified in the policy store during initialization. Cedarling uses the JWT `iss` claim to select the appropriate keys for validation.

In an enterprise deployment, the Cedarling can also check for JWT revocation. The Cedarling
checks the status following a mechanism described in the
[OAuth Status Lists](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/)
draft. Enforcing the status of tokens helps limit the damage of account takeover--i.e. to immediately
recursively revoke all the tokens issued to an attacker. Domains may want to use Token Status also to
implement single-transaction tokens.
### JWT Revocation

Here is a summary of the ways the Cedarling may validate a JWT, depending on your bootstrap properties:
In enterprise deployments, Cedarling can also check for JWT revocation. This is achieved by following the mechanism described in the [OAuth Status Lists](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) draft. Token status enforcement helps mitigate risks associated with account takeover by enabling immediate revocation of all tokens issued to an attacker. Additionally, domains may choose to use Token Status to implement single-use transaction tokens.

* Validate signature from Trusted Issuer
* Check JWT status
* Discard id_token if `aud` does not match access_token `client_id`
* Discard Userinfo token not associated with a `sub` from the id_token
* Check access token and id_token `exp` and `nbf` claims if time sent in Context
### Summary of JWT Validation Mechanisms

Depending on your bootstrap properties, Cedarling may validate JWTs through the following methods:

![](./assets/lock-cedarling-diagram-4.jpg)
- Validate signatures from Trusted Issuers
- Check JWT status for revocation
- Discard `id_token` if the `aud` claim does not match the `client_id` of the access token
- Discard Userinfo tokens that are not associated with a `sub` claim from the `id_token`
- Verify `exp` (expiration) and `nbf` (not before) claims of access tokens and id_tokens, if timestamps are provided in the context

![](./assets/lock-cedarling-diagram-4.jpg)
185 changes: 102 additions & 83 deletions docs/cedarling/cedarling-policy-store.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,122 +10,141 @@ tags:

# Cedarling Policy Store

By convention, the filename is `cedarling_store.json`. It contains all the data the
Cedarling needs to evaluate policies and verify JWT tokens:
The Cedarling Policy Store uses a JSON file named `cedarling_store.json` to store all necessary data for evaluating policies and verifying JWT tokens. The structure includes the following key components:

1. Cedar Schema - Base64 encoded human format
2. Cedar Policies - Base64 encoded human format
3. Trusted Issuers - See below syntax
1. **Cedar Schema**: The Cedar schema encoded in Base64.
2. **Cedar Policies**: The Cedar policies encoded in Base64.
3. **Trusted Issuers**: Details about the trusted issuers (see [below](#trusted-issuer-schema) for syntax).

The JSON schema looks like this:
**Note:** The `cedarling_store.json` file is only needed if the bootstrap properties: `CEDARLING_LOCK`; `CEDARLING_POLICY_STORE_URI`; and `CEDARLING_POLICY_STORE_ID` are not set to a local location. If you're fetching the policies remotely, you don't need a `cedarling_store.json` file.

```
### JSON Schema

The JSON Schema is for the `policy_store.json` defined as follows:

```json
{
"policystore_id": "...",
"policies": {...},
"schema": "...",
"trusted_issuers": [],
"cedar_version": "v1.1.5"
"cedar_version": "v2.4.7",
"cedar_policies": { ... },
"cedar_schema": "cedar_schema_encoded_in_base64",
"trusted_issuers": [ ... ]
}

```

- **policystore_id** : (String, no spaces) The unique identifier for the policy store.
- **policies** : (Json) Json object containing one or more policy_id as key and policy object as value
- **schema** : (String) Base64 encoded cedar schema
- **trusted_issuers** : (List) List of Trusted Issuer metadata
- **cedar_version** : (String) The version of [Cedar policy](https://docs.cedarpolicy.com/). The protocols of this version will be followed when processing Cedar schema and policies.
- **cedar_version** : (*String*) The version of [Cedar policy](https://docs.cedarpolicy.com/). The protocols of this version will be followed when processing Cedar schema and policies.
- **cedar_policies** : (*Object*) Object containing one or more policy IDs as keys, with their corresponding objects as values. See: [cedar_policies schema](#cedar-policies-schema).
- **cedar_schema** : (*String*) The Cedar schema, encoded in Base64 format.
- **trusted_issuers** : (*Array of [TrustedIssuer](#trusted-issuer-schema)*) List of metadata for Trusted Issuers.

## Trusted Issuer Schema
## Cedar Policies Schema

This record contains the information needed to validate tokens from this issuer:
The `cedar_policies` field outlines the Cedar policies that will be used in Cedarling. Multiple policies can be defined, with policy requiring a unique `policy_id`.

```json
"cedar_policies": {
"a_unique_policy_id": {
"description": "simple policy example",
"creation_date": "2024-09-20T17:22:39.996050",
"policy_content": "cGVybWl0KAogICAgc..."
},
...
}
```
"trusted_issuers": [
{
"name": "name_of_the_trusted_issuer",
"description": "description_of_the_trusted_issuer",
"openid_configuration_endpoint": "https://<trusted-issuer-hostname>/.well-known/openid-configuration",
"token_metadata": [token1_entity_schema, token2_entity_schema, ... ]
}
]
```

- **name** : (String) Name of the trusted issuer
- **description** : (String) Short description of the trusted issuer
- **openid_configuration_endpoint** : The HTTPS URL for the OpenID Connect configuration endpoint (usually found under /.well-known/openid-configuration).
- **token_metadata** : (List) List of token metadata
- **unique_policy_id**: (*String*) A uniqe policy ID used to for tracking and auditing purposes.
- **description** : (*String*) A brief description of cedar policy
- **creation_date** : (*String*) Policy creating date in `YYYY-MM-DDTHH:MM:SS.ssssss`
- **policy_content** : (*String*) The policy content, encoded in Base64 format.

## Policies
### Example of `cedar_policies`

Json object containing one or more policy_id as key and policy object as value.
Here is a non-normative example of the `cedar_policies` field:

```
"policies": {
```json
"cedar_policies": {
"840da5d85403f35ea76519ed1a18a33989f855bf1cf8": {
"description": "simple policy example",
"creation_date": "2024-09-20T17:22:39.996050",
"policy_content": "cGVybWl0KAogICAgc..."
}
},
"0fo1kl928Afa0sc9123scma0123891asklajsh1233ab": {
"description": "another policy example",
"creation_date": "2024-09-20T18:22:39.192051",
"policy_content": "kJW1bWl0KA0g3CAxa..."
},
...
}
```

- **description** : (String) Short description of cedar policy
- **creation_date** : (String) Policy creating date in `YYYY-MM-DDTHH:MM:SS.ssssss`
- **policy_content** : (String) Base64 encoded cedar schema

## Trusted Issuer Schema

## Token Entity Schema
This record contains the information needed to validate tokens from this issuer:

```json
"token_metadata": [ ... ]
}
]
```
{ "type": "access"
"user_id": "...",
"role_mapping": "...",

- **name** : (*String*) The name of the trusted issuer.
- **description** : (*String*) A brief description of the trusted issuer, providing context for administrators.
- **openid_configuration_endpoint** : (*String*) The HTTPS URL for the OpenID Connect configuration endpoint (usually found at `/.well-known/openid-configuration`).
- **token_metadata** : (*Array of [TokenMetadata](#token-metadata-schema)*, *optional*) Metadata related to the tokens issued by this issuer.

### Token Metadata Schema


```json
{
"type": "access_token"
"user_id": "some_user123",
"role_mapping": "role",
}
```

- **type** : (String) The type of token whether `access`, `id_token`, `userinfo` or `transaction`.
- **user_id** : (String) For id_token or userinfo tokens, the `user_id` refers to the claim used to identify the person entity. For example, in an id_token, the "user_id" could be the sub (subject) or email claim.
- **role_mapping** : (String) The role_mapping refers to the token claim used to get role values. The default value of `role_mapping` is `role`.
- **type** : (String, no spaces) The type of token (e.g., Access, ID, Userinfo, Transaction).
- **user_id** : (String) The claim used to create the Cedar entity associated with this token.
- **role_mapping** : (String, *optional*) The claim used to create a role for the token. The default value of `role_mapping` is `role`.

### Sample Policy store
**Note**: Only one token should include the `role_mapping` field in the list of `token_metadata`.

```
## Example Policy store

Here is a non-normative example of a `cedarling_store.json` file:

```json
{
"policystore_id": "8b805e22fdd39f3dd33a13d9fb446d8e6314153ca997",
"name": "gluustore",
"description": "gluu",
"policies": {
"840da5d85403f35ea76519ed1a18a33989f855bf1cf8": {
"description": "simple policy example",
"creation_date": "2024-09-20T17:22:39.996050",
"policy_content": "cGVybWl0KAogICAgc..."
}
},
"trusted_issuers": [
{
"name": "Google",
"description": "Consumer IDP",
"openid_configuration_endpoint": "https://accounts.google.com/.well-known/openid-configuration",
"token_metadata": [
{
"type": "access",
"user_id": "aud"
},
{
"type": "id_token",
"user_id": "email",
"role_mapping": "role"
},
"cedar_version": "v2.4.7",
"cedar_policies": {
"840da5d85403f35ea76519ed1a18a33989f855bf1cf8": {
"description": "simple policy example",
"creation_date": "2024-09-20T17:22:39.996050",
"policy_content": "cedar_policy_encoded_in_base64"
}
},
"cedar_schema": "cedar_schema_encoded_in_base64",
"trusted_issuers": [
{
"type": "userinfo",
"user_id": "email"
"name": "Google",
"description": "Consumer IDP",
"openid_configuration_endpoint": "https://accounts.google.com/.well-known/openid-configuration",
"token_metadata": [
{
"type": "access_token",
"user_id": "aud"
},
{
"type": "id_token",
"user_id": "sub"
},
{
"type": "userinfo_token",
"user_id": "email",
"role_mapping": "role"
}
]
}
]
}
],
"schema": "ewogICAgIkphbnMiOiB...",
"cedar_version": "v2.4.7"
]
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,14 @@ pub struct PolicyStoreConfig {
/// Source - represent the place where we going to read the policy.
/// The value is required.
pub source: Option<PolicyStoreSource>,
/// `CEDARLING_POLICY_STORE_ID` in [bootstrap properties](https://github.com/JanssenProject/jans/wiki/Cedarling-Nativity-Plan#bootstrap-properties) documentation.
/// If None then we should have only one policy store in the `source`.
pub store_id: Option<String>,
}

#[pymethods]
impl PolicyStoreConfig {
#[new]
#[pyo3(signature = (source=None, store_id=None))]
fn new(source: Option<PolicyStoreSource>, store_id: Option<String>) -> PyResult<Self> {
Ok(PolicyStoreConfig { source, store_id })
#[pyo3(signature = (source=None ))]
fn new(source: Option<PolicyStoreSource>) -> PyResult<Self> {
Ok(PolicyStoreConfig { source })
}
}

Expand All @@ -66,9 +63,6 @@ impl TryInto<cedarling::PolicyStoreConfig> for PolicyStoreConfig {
))?
.into();

Ok(cedarling::PolicyStoreConfig {
source,
store_id: self.store_id,
})
Ok(cedarling::PolicyStoreConfig { source })
}
}
1 change: 1 addition & 0 deletions jans-cedarling/cedarling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ url = "2.5.2"
lazy_static = "1.5.0"
jsonwebtoken = "9.3.0"
typed-builder = "0.20.0"
semver = "1.0.23"
derive_more = { version = "1.0.0", features = ["deref"] }

[dev-dependencies]
Expand Down
1 change: 0 additions & 1 deletion jans-cedarling/cedarling/examples/authorize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
},
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Json(POLICY_STORE_RAW.to_string()),
store_id: None,
},
jwt_config: JwtConfig::Disabled,
})?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
},
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Json(POLICY_STORE_RAW.to_string()),
store_id: None,
},
jwt_config,
})?;
Expand Down
1 change: 0 additions & 1 deletion jans-cedarling/cedarling/examples/log_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
log_config: LogConfig { log_type },
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Json(POLICY_STORE_RAW.to_string()),
store_id: None,
},
jwt_config: JwtConfig::Disabled,
})?;
Expand Down
Loading

0 comments on commit 98f3d5b

Please sign in to comment.