Skip to content

Commit

Permalink
feat(jans-cedarling): implement new bootstrap configs for JWT validat…
Browse files Browse the repository at this point in the history
…ion (#10306)

* feat(jans-cedarling): implement NewJwtConfig

- implement the NewJwtConfig which contains the updated bootstrap
  properties and some helper methods for initialization.

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

* refactor(jans-cedarling): move HttpClient and it's tests closer to root

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

* feat(jans-cedarling): implement loading JWKS for NewJwtService

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

* chore(jans-cedarling): remove failing example docstring from private module

Removed example docstring in a private module due to test failures. The examples
could not import the necessary structs because they are private,
causing `cargo test` to fail.

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

* chore(jans-cedarling): update a docstring in NewJwtConfig

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

* chore(jans-cedarling): remove unused structs

- remove AccessTokenValidationConfig
- remove UserinfoTokenValidationConfig
- remove IdTokenValidationConfig

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

* feat(jans-cedarling): enhance JwkStore to support loading from JwkSet and TrustedIssuer

- Added `new_from_jwkset` method to initialize `JwkStore` from a JWK set.
- Added `new_from_trusted_issuer` method to initialize `JwkStore` using a TrustedIssuer.
- Implemented `get` method to easily fetch keys by Key ID.
- Added support for storing and handling keys without Key IDs.

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

* feat(jans-cedarling): implement a new KeyService

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

* feat(jans-cedarling): start new implementation for token Validator

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

* feat(jans-cedarling): simplify JwtValidatorConfig

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

* feat(jans-cedarling): implement new check_missing_claims function

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

* feat(jans-cedarling): gracefully handle JWKS with unsupported algs

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

* feat(jans-cedarling): optimize JwtValidator initialization for reusable validators

- refactor the JwtValidator startup process to support reusing existing validators,
  reducing redundant initialization and improving performance.

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

* feat(jans-cedarling): implement init and process for NewJwtService

- implement new_from_config for NewJwtService
- implement process_tokens for NewJwtService

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

* feat(jans-cedarling): return TrustedIssuer info with jwt validation result

- Implement returning TrustedIssuer information with the JWT validation
  result to be able to find the mappings used for Cedar easily.

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

* refactor(jans-cedarling): remove local jwks and issuers from NewJwtConfig

- remove local jwks and trusted issuers from NewJwtConfig.
- local jwks and trusted issuers should be passed separately via the
  new_with_local_jwks or new_with_trusted_issuers functions.

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

* refactor(jans-cedarling): add back local jwks into NewJwtConfig

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

* feat(jans-cedarling): add NewJwtService to ServiceFactory

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

* feat(jans-cedarling): enhance JwtValidator initialization

- allow initialization of JwtValidator even if there's no JWKS or
  trusted issuer provided as long as signature validator is turned off.

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

* feat(jans-cedarling): add NewJwtService to AuthzConfig

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

* feat(jans-cedarling): change process_tokens's result

- Change process_token's result to be DecodeTokensResult so it would be
  compatible with the existing calls.

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

* feat(jans-cedarling): enchance JwtValidator implementation

- implement returning a reference to the TrustedIssuer when decoding without
  signature validation
- implement checking if the scheme of the token's `iss` is `https`

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

* refactor(jans-cedarling): replace old JwtService implementation with new

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

* chore(jans-cedarling): rename JwtServiceError to JwtProcessingError

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

* chore(jans-cedarling): rename DecodeTokensResult to ProcessTokensResult

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

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

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

* chore(jans-cedarling): remove unnecessary println! calls

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

* chore(jans-cedarling): delete unused files

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

* chore(jans-cedarling): add copyright information on top of files

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

* docs(jans-cedarling): update cedarling-properties.md

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

* chore(jans-cedarling): resolve clippy issue with elided lifetime

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

* chore(jans-cedarling): minor spelling corrections

Signed-off-by: John Anderson <[email protected]>

---------

Signed-off-by: rmarinn <[email protected]>
Signed-off-by: John Anderson <[email protected]>
Co-authored-by: John Anderson <[email protected]>
  • Loading branch information
rmarinn and djellemah authored Dec 2, 2024
1 parent 95561a2 commit a2dee8c
Show file tree
Hide file tree
Showing 49 changed files with 2,641 additions and 3,324 deletions.
16 changes: 16 additions & 0 deletions docs/cedarling/cedarling-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ Below is an example of a bootstrap config in JSON format.

- Note that properties set to `"disabled"`, an empty string `""`, zero `0`, and `null` can be ommited since they are the defaults.

#### Local JWKS

A local JWKS can be used by setting the `CEDARLING_LOCAL_JWKS` bootstrap property to a path to a local JSON file. When providing a local Json Web Key Store (JWKS), the file must follow the following schema:

```json
{
"trusted_issuer_id": [ ... ]
"another_trusted_issuer_id": [ ... ]
}
```

- Where keys are `Trusted Issuer IDs` assigned to each key store
- and the values contains the JSON Web Keys as defined in [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517).
- The `trusted_issuers_id` is used to tag a JWKS with a unique identifier and enables using multiple key stores.


### Loading From YAML

Below is an example of a bootstrap config in YAML format.
Expand Down
2 changes: 1 addition & 1 deletion jans-cedarling/bindings/cedarling_python/PYTHON_TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Example
```python
from cedarling import BootstrapConfig
# Example configuration
bootstrap_config = NewBootstrapConfig({
bootstrap_config = BootstrapConfig({
"application_name": "MyApp",
"policy_store_uri": None,
"policy_store_id": "policy123",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ create_exception!(
"Error encountered while decoding JWT token data"
);

create_exception!(
authorize_errors,
ProcessTokens,
AuthorizeError,
"Error encountered while processing JWT token data"
);

create_exception!(
authorize_errors,
AccessTokenEntitiesError,
Expand Down Expand Up @@ -138,7 +145,7 @@ macro_rules! errors_functions {
// This function is used to convert `cedarling::AuthorizeError` to a Python exception.
// For each possible case of `AuthorizeError`, we have created a corresponding Python exception that inherits from `cedarling::AuthorizeError`.
errors_functions! {
DecodeTokens => DecodeTokens,
ProcessTokens => ProcessTokens,
AccessTokenEntities => AccessTokenEntitiesError,
CreateIdTokenEntity => CreateIdTokenEntityError,
CreateUserinfoTokenEntity => CreateUserinfoTokenEntityError,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

use cedarling::bindings::PolicyStore;
use cedarling::{BootstrapConfigRaw, LoggerType, TrustMode, WorkloadBoolOp};
use cedarling::{BootstrapConfigRaw, IdTokenTrustMode, LoggerType, WorkloadBoolOp};
use jsonwebtoken::Algorithm;
use pyo3::exceptions::{PyKeyError, PyValueError};
use pyo3::prelude::*;
Expand All @@ -17,7 +17,7 @@ use std::str::FromStr;
/// BootstrapConfig
/// ===================
///
/// A Python wrapper for the Rust `NewBootstrapConfig` struct.
/// A Python wrapper for the Rust `BootstrapConfig` struct.
/// Configures the application, including authorization, logging, JWT validation, and policy store settings.
///
/// Attributes
Expand Down Expand Up @@ -65,7 +65,7 @@ use std::str::FromStr;
/// from cedarling import BootstrapConfig
///
/// # Example configuration
/// bootstrap_config = NewBootstrapConfig(
/// bootstrap_config = BootstrapConfig(
/// application_name="MyApp",
/// policy_store_uri=None,
/// policy_store_id="policy123",
Expand Down Expand Up @@ -508,7 +508,7 @@ impl TryFrom<BootstrapConfig> for cedarling::BootstrapConfig {
.userinfo_exp_validation
.try_into()
.map_err(|e| PyValueError::new_err(format!("{}", e)))?,
id_token_trust_mode: TrustMode::from_str(value.id_token_trust_mode.as_str())
id_token_trust_mode: IdTokenTrustMode::from_str(value.id_token_trust_mode.as_str())
.unwrap_or_default(),
lock: value
.lock
Expand Down
72 changes: 0 additions & 72 deletions jans-cedarling/bindings/cedarling_python/src/config/jwt_config.rs

This file was deleted.

3 changes: 0 additions & 3 deletions jans-cedarling/bindings/cedarling_python/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ use pyo3::prelude::*;
use pyo3::Bound;

pub(crate) mod bootstrap_config;
mod jwt_config;

pub fn register_entities(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<bootstrap_config::BootstrapConfig>()?;
m.add_class::<jwt_config::JwtConfig>()?;

Ok(())
}
1 change: 1 addition & 0 deletions jans-cedarling/cedarling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ derive_more = { version = "1.0.0", features = [
"display",
"error",
] }
time = { version = "0.3.36", features = ["wasm-bindgen"] }
regex = "1.11.1"

[dev-dependencies]
Expand Down
16 changes: 12 additions & 4 deletions jans-cedarling/cedarling/examples/authorize_with_jwt_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
*/

use cedarling::{
AuthorizationConfig, BootstrapConfig, Cedarling, JwtConfig, LogConfig, LogTypeConfig,
PolicyStoreConfig, PolicyStoreSource, Request, ResourceData, WorkloadBoolOp,
AuthorizationConfig, BootstrapConfig, Cedarling, IdTokenTrustMode, JwtConfig, LogConfig,
LogTypeConfig, PolicyStoreConfig, PolicyStoreSource, Request, ResourceData,
TokenValidationConfig, WorkloadBoolOp,
};
use jsonwebtoken::Algorithm;
use std::collections::{HashMap, HashSet};
Expand All @@ -19,8 +20,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Configure JWT validation settings. Enable the JwtService to validate JWT tokens
// using specific algorithms: `HS256` and `RS256`. Only tokens signed with these algorithms
// will be accepted; others will be marked as invalid during validation.
let jwt_config = JwtConfig::Enabled {
signature_algorithms: HashSet::from_iter([Algorithm::HS256, Algorithm::RS256]),
let jwt_config = JwtConfig {
jwks: None,
jwt_sig_validation: true,
jwt_status_validation: false,
id_token_trust_mode: IdTokenTrustMode::None,
signature_algorithms_supported: HashSet::from_iter([Algorithm::HS256, Algorithm::RS256]),
access_token_config: TokenValidationConfig::access_token(),
id_token_config: TokenValidationConfig::id_token(),
userinfo_token_config: TokenValidationConfig::userinfo_token(),
};

// You must change this with your own tokens
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

use cedarling::{
AuthorizationConfig, BootstrapConfig, Cedarling, JwtConfig, LogConfig, LogTypeConfig,
AuthorizationConfig, BootstrapConfig, Cedarling, LogConfig, LogTypeConfig, JwtConfig,
PolicyStoreConfig, PolicyStoreSource, Request, ResourceData, WorkloadBoolOp,
};
use std::collections::HashMap;
Expand All @@ -22,7 +22,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
},
jwt_config: JwtConfig::Disabled,
jwt_config: JwtConfig::new_without_validation(),
authorization_config: AuthorizationConfig {
use_user_principal: true,
use_workload_principal: true,
Expand Down
2 changes: 1 addition & 1 deletion jans-cedarling/cedarling/examples/log_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
policy_store_config: PolicyStoreConfig {
source: PolicyStoreSource::Yaml(POLICY_STORE_RAW.to_string()),
},
jwt_config: JwtConfig::Disabled,
jwt_config: JwtConfig::new_without_validation(),
authorization_config: AuthorizationConfig {
use_user_principal: true,
use_workload_principal: true,
Expand Down
7 changes: 7 additions & 0 deletions jans-cedarling/cedarling/src/authz/authorize_result.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/*
* This software is available under the Apache-2.0 license.
* See https://www.apache.org/licenses/LICENSE-2.0.txt for full text.
*
* Copyright (c) 2024, Gluu, Inc.
*/

use cedar_policy::Decision;

use crate::bootstrap_config::WorkloadBoolOp;
Expand Down
8 changes: 4 additions & 4 deletions jans-cedarling/cedarling/src/authz/entities/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use create::{build_entity_uid, create_entity, parse_namespace_and_typename, Enti
use super::request::ResourceData;
use super::token_data::TokenPayload;

pub(crate) type DecodeTokensResult<'a> =
jwt::DecodeTokensResult<'a, AccessTokenData, IdTokenData, UserInfoTokenData>;
pub(crate) type ProcessTokensResult<'a> =
jwt::ProcessTokensResult<'a, AccessTokenData, IdTokenData, UserInfoTokenData>;

/// Access token entities
pub struct AccessTokenEntities {
Expand Down Expand Up @@ -117,7 +117,7 @@ pub fn create_id_token_entity(
/// Create user entity
pub fn create_user_entity(
policy_store: &PolicyStore,
tokens: &DecodeTokensResult,
tokens: &ProcessTokensResult,
parents: HashSet<EntityUid>,
trusted_issuer: &TrustedIssuer,
) -> Result<cedar_policy::Entity, CedarPolicyCreateTypeError> {
Expand Down Expand Up @@ -211,7 +211,7 @@ pub enum RoleEntityError {
/// Create `Role` entity from based on `TrustedIssuer` or default value of `RoleMapping`
pub fn create_role_entities(
policy_store: &PolicyStore,
tokens: &DecodeTokensResult,
tokens: &ProcessTokensResult,
trusted_issuer: &TrustedIssuer,
) -> Result<Vec<cedar_policy::Entity>, RoleEntityError> {
// get role mapping or default value
Expand Down
21 changes: 14 additions & 7 deletions jans-cedarling/cedarling/src/authz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ mod token_data;
pub use authorize_result::AuthorizeResult;
use cedar_policy::{Entities, Entity, EntityUid, Response};
use entities::CedarPolicyCreateTypeError;
use entities::DecodeTokensResult;
use entities::ProcessTokensResult;
use entities::ResourceEntityError;
use entities::{
create_access_token_entities, create_id_token_entity, create_role_entities, create_user_entity,
Expand All @@ -53,7 +53,6 @@ pub(crate) struct AuthzConfig {
/// Authorization Service
/// The primary service of the Cedarling application responsible for evaluating authorization requests.
/// It leverages other services as needed to complete its evaluations.
#[allow(dead_code)]
pub struct Authz {
config: AuthzConfig,
authorizer: cedar_policy::Authorizer,
Expand Down Expand Up @@ -211,13 +210,13 @@ impl Authz {
let policy_store = &self.config.policy_store;

// decode JWT tokens to structs AccessTokenData, IdTokenData, UserInfoTokenData using jwt service
let decode_result: DecodeTokensResult = self
let decode_result: ProcessTokensResult = self
.config
.jwt_service
.decode_tokens::<AccessTokenData, IdTokenData, UserInfoTokenData>(
.process_tokens::<AccessTokenData, IdTokenData, UserInfoTokenData>(
&request.access_token,
&request.id_token,
&request.userinfo_token,
Some(&request.userinfo_token),
)?;

let trusted_issuer = decode_result.trusted_issuer.unwrap_or_default();
Expand Down Expand Up @@ -312,12 +311,20 @@ impl AuthorizeEntitiesData {
}
}

/// Error type for Authorization Service
#[derive(thiserror::Error, Debug)]
pub enum AuthzInitError {
/// Error encountered while Initializing [`JwtService`]
#[error(transparent)]
JwtService(#[from] jwt::JwtServiceInitError),
}

/// Error type for Authorization Service
#[derive(thiserror::Error, Debug)]
pub enum AuthorizeError {
/// Error encountered while decoding JWT token data
/// Error encountered while processing JWT token data
#[error(transparent)]
DecodeTokens(#[from] jwt::JwtServiceError),
ProcessTokens(#[from] jwt::JwtProcessingError),
/// Error encountered while creating access token entities
#[error("{0}")]
AccessTokenEntities(#[from] AccessTokenEntitiesError),
Expand Down
Loading

0 comments on commit a2dee8c

Please sign in to comment.