Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a130aaa
chore(jans-cedarling): refactor and add `IssuerIndex` thread safe str…
olehbozhok Jan 23, 2026
8c89e89
refactor(jans-cedarling): add type `IssClaim` that should hold normal…
olehbozhok Jan 23, 2026
720a0fa
chore(jans-cedarling): fix clippy issues
olehbozhok Jan 23, 2026
cc7c720
chore(jans-cedarling): fix clippy warn `field ___ is never read`
olehbozhok Jan 23, 2026
a0115ce
feat(jans-cedarling): add thread safety to `JwtValidatorCache`
olehbozhok Jan 23, 2026
f844b47
feat(jans-cedarling): add thread safety to `KeyService`
olehbozhok Jan 23, 2026
45d0887
feat(jans-cedarling): add new bootstrap property `CEDARLING_TRUSTED_…
olehbozhok Jan 23, 2026
99ed28f
chore(jans-cedarling): refactor to use `TrustedIssuerLoader` on loadi…
olehbozhok Jan 23, 2026
bbf870c
chore(jans-cedarling): add method `TrustedIssuerLoader::load_trusted_…
olehbozhok Jan 27, 2026
24cc68e
feat(jans-cedarling): implement asynchronous loading trusted issuers
olehbozhok Jan 27, 2026
a183dc3
chore(jans-cedarling): update agent file
olehbozhok Jan 27, 2026
bc62313
test(jans-cedarling): add unit tests to `TrustedIssuerLoader`
olehbozhok Jan 27, 2026
513d158
Merge commit 'main' into jans-cedarling-10004
olehbozhok Jan 29, 2026
9bc827f
chore(jans-cedarling): automatic clippy fixes
olehbozhok Jan 29, 2026
9f0375b
chore(jans-cedarling): fix clippy issuers
olehbozhok Jan 29, 2026
4732690
chore(jans-cedarling): fix clippy issuers
olehbozhok Jan 29, 2026
2636d75
feat(jans-cedarling): updated bootstrap properties to have 2 loader t…
olehbozhok Feb 2, 2026
10c9f7b
docs(jans-cedarling): update bootstrap properties reference with miss…
olehbozhok Feb 2, 2026
9b950e8
Merge commit 'b511d61b20737ebb21d04df49197c6fcfbf329fa' into jans-ced…
olehbozhok Feb 2, 2026
46d3386
chore(jans-cedarling): remove outdated comment
olehbozhok Feb 2, 2026
a4cfec4
chore(jans-cedarling): fix clippy issues
olehbozhok Feb 2, 2026
61b702a
chore(jans-cedarling): fix mistype `retreive` -> `retrieve`
olehbozhok Feb 2, 2026
bc26ed6
chore(jans-cedarling): remove unused import
olehbozhok Feb 2, 2026
5fdd95a
chore(jans-cedarling): add license to file
olehbozhok Feb 2, 2026
48911bc
chore(jans-cedarling): fix using docstring instead of comments
olehbozhok Feb 2, 2026
54b6488
chore(jans-cedarling): simplify using lifetimes
olehbozhok Feb 2, 2026
4b81c8d
chore(jans-cedarling): fix clippy issues
olehbozhok Feb 2, 2026
86ef562
chore(jans-cedarling): remove unwrap and use expect with messages
olehbozhok Feb 2, 2026
95f8932
chore(jans-cedarling): Remove unused _id variable.
olehbozhok Feb 2, 2026
965ac00
chore(jans-cedarling): simplify Issuer equality check
olehbozhok Feb 2, 2026
edf8370
chore(jans-cedarling): fix clippy issues
olehbozhok Feb 2, 2026
c8f230b
docs(jans-cedarling): improve doc message to `CEDARLING_TRUSTED_ISSUE…
olehbozhok Feb 2, 2026
73b0b29
test(jans-cedarling): add tests loading `CEDARLING_TRUSTED_ISSUER_LOA…
olehbozhok Feb 2, 2026
2d4aa2b
chore(jans-cedarling): improvement to use NonZeroUsize::MIN
olehbozhok Feb 4, 2026
7ede7b2
chore(jans-cedarling): remove arc wrapper for `StatusListCache`
olehbozhok Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions docs/cedarling/reference/cedarling-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ These Bootstrap Properties control default application level behavior.

To enable usage of principals at least one of the following keys must be provided:

- **`CEDARLING_WORKLOAD_AUTHZ`** : When `enabled`, Cedar engine authorization is queried for a Workload principal.
- **`CEDARLING_WORKLOAD_AUTHZ`** : When `enabled`, Cedar engine authorization is queried for a Workload principal. Default is `disabled`.

- **`CEDARLING_USER_AUTHZ`** : When `enabled`, Cedar engine authorization is queried for a User principal.
- **`CEDARLING_USER_AUTHZ`** : When `enabled`, Cedar engine authorization is queried for a User principal. Default is `disabled`.

To load policy store one of the following keys must be provided:

Expand Down Expand Up @@ -58,6 +58,10 @@ All other fields are optional and can be omitted. If a field is not provided, Ce

- **`CEDARLING_TOKEN_CACHE_EARLIEST_EXPIRATION_EVICTION`** : Enables eviction policy based on the earliest expiration time. When the cache reaches its capacity, the entry with the nearest expiration timestamp will be removed to make room for a new one. Default value is `true`.

- **`CEDARLING_TRUSTED_ISSUER_LOADER_TYPE`** : `SYNC` | `ASYNC` -- Type of trusted issuer loader. If not set, synchronous loader is used. Sync loader means that trusted issuers will be loaded on initialization. Async loader means that trusted issuers will be loaded in background. Default is `SYNC`.

- **`CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS`** : Number of concurrent workers to use when loading trusted issuers. Applies to both `SYNC` (parallel loading during initialization) and `ASYNC` (parallel background loading) modes. Default value is 1. Zero will be treated as default value.

**Cedar Entity Mapping properties**

- **`CEDARLING_MAPPING_USER`** : Name of Cedar User schema entity if we don't want to use default. When specified Cedarling try build defined entity (from schema) as user instead of default `User` entity defined in `cedar` schema. Works in namespace defined in the policy store. Default value: `Jans::User`.
Expand All @@ -68,7 +72,7 @@ All other fields are optional and can be omitted. If a field is not provided, Ce

**The following bootstrap properties are needed to configure log behavior:**

- **`CEDARLING_LOG_TYPE`** : `off`, `memory`, `std_out`
- **`CEDARLING_LOG_TYPE`** : `off`, `memory`, `std_out`. Default is `off`.
- **`CEDARLING_LOG_LEVEL`** : System Log Level [See here](./cedarling-logs.md). Default to `WARN`
- **`CEDARLING_DECISION_LOG_USER_CLAIMS`** : List of claims to map from user entity, such as ["sub", "email", "username", ...]
- **`CEDARLING_DECISION_LOG_WORKLOAD_CLAIMS`** : List of claims to map from workload entity, such as ["client_id", "rp_id", ...]
Expand All @@ -84,9 +88,9 @@ All other fields are optional and can be omitted. If a field is not provided, Ce

- **`CEDARLING_LOCAL_JWKS`** : Path to a local file containing a JWKS

- **`CEDARLING_JWT_SIG_VALIDATION`** : `enabled` | `disabled` -- Whether to check the signature of all JWT tokens. This requires an `iss` is present.
- **`CEDARLING_JWT_STATUS_VALIDATION`** : `enabled` | `disabled` -- Whether to check the status of the JWT. On startup, the Cedarling should fetch and retreive the latest Status List JWT from the `.well-known/openid-configuration` via the `status_list_endpoint` claim and cache it. See the [IETF Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) for more info.
- **`CEDARLING_JWT_SIGNATURE_ALGORITHMS_SUPPORTED`** : Only tokens signed with these algorithms are acceptable to the Cedarling.
- **`CEDARLING_JWT_SIG_VALIDATION`** : `enabled` | `disabled` -- Whether to check the signature of all JWT tokens. This requires an `iss` is present. Default is `disabled`.
- **`CEDARLING_JWT_STATUS_VALIDATION`** : `enabled` | `disabled` -- Whether to check the status of the JWT. On startup, the Cedarling should fetch and retrieve the latest Status List JWT from the `.well-known/openid-configuration` via the `status_list_endpoint` claim and cache it. See the [IETF Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/) for more info. Default is `disabled`.
- **`CEDARLING_JWT_SIGNATURE_ALGORITHMS_SUPPORTED`** : Only tokens signed with these algorithms are acceptable to the Cedarling. If not specified, all algorithms supported by the underlying library are allowed.
- **`CEDARLING_ID_TOKEN_TRUST_MODE`** : `strict` | `never` | `always` | `ifpresent`. Varying levels of validations based on the preference of the developer.

- **`strict`** (default): Enforces strict validation rules:
Expand All @@ -103,12 +107,12 @@ All other fields are optional and can be omitted. If a field is not provided, Ce

**The following bootstrap properties are only needed for the Lock Server Integration.**

- **`CEDARLING_LOCK`** : `enabled` | `disabled`. If `enabled`, the Cedarling will connect to the Lock Server for policies, and subscribe for SSE events.
- **`CEDARLING_LOCK`** : `enabled` | `disabled`. If `enabled`, the Cedarling will connect to the Lock Server for policies, and subscribe for SSE events. Default is `disabled`.
- **`CEDARLING_LOCK_SERVER_CONFIGURATION_URI`** : Required if `LOCK` == `enabled`. URI where Cedarling can get JSON file with all required metadata about the Lock Server, i.e. `.well-known/lock-master-configuration`.
- **`CEDARLING_LOCK_DYNAMIC_CONFIGURATION`** : `enabled` | `disabled`, controls whether Cedarling should listen for SSE config updates.
- **`CEDARLING_LOCK_DYNAMIC_CONFIGURATION`** : `enabled` | `disabled`, controls whether Cedarling should listen for SSE config updates. Default is `disabled`.
- **`CEDARLING_LOCK_SSA_JWT`** : SSA for DCR in a Lock Server deployment. The Cedarling will validate this SSA JWT prior to DCR.
- **`CEDARLING_LOCK_LOG_INTERVAL`** : How often to send log messages to Lock Server (0 to turn off transmission).
- **`CEDARLING_LOCK_HEALTH_INTERVAL`** : How often to send health messages to Lock Server (0 to turn off transmission).
- **`CEDARLING_LOCK_TELEMETRY_INTERVAL`** : How often to send telemetry messages to Lock Server (0 to turn off transmission).
- **`CEDARLING_LOCK_LISTEN_SSE`** : `enabled` | `disabled`: controls whether Cedarling should listen for updates from the Lock Server.
- **`CEDARLING_LOCK_ACCEPT_INVALID_CERTS`** : `enabled` | `disabled`: Allows interaction with a Lock server with invalid certificates. Mainly used for testing. Doesn't work for WASM builds.
- **`CEDARLING_LOCK_LISTEN_SSE`** : `enabled` | `disabled`: controls whether Cedarling should listen for updates from the Lock Server. Default is `disabled`.
- **`CEDARLING_LOCK_ACCEPT_INVALID_CERTS`** : `enabled` | `disabled`: Allows interaction with a Lock server with invalid certificates. Mainly used for testing. Doesn't work for WASM builds. Default is `disabled`.
8 changes: 5 additions & 3 deletions jans-cedarling/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@
- Use test_utils crate for shared test helpers
- Follow existing test patterns in authorize_*.rs files
- Include both positive and negative test cases
- For error checking, use `assert!(matches!(...), "explicit comment")` instead of `assert!(result.is_err())`
- Use `expect_err("explicit comment")` instead of `panic()`
- Always include explicit comments explaining what error is expected
- For success checking, use `result.expect("descriptive message")` instead of `assert!(result.is_ok())`
- For error checking, use `result.expect_err("descriptive message")` instead of `assert!(result.is_err())`
- For pattern matching errors, use `assert!(matches!(...), "descriptive message")`
- All assertions must include a descriptive message explaining what is being tested
- Use `expect_err("explicit comment")` instead of `panic()` when expecting errors

**File Headers:**
- Each Rust file must contain the Apache 2.0 license header:
Expand Down
5 changes: 4 additions & 1 deletion jans-cedarling/cedarling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ async-trait = "0.1.88"
futures = "0.3.31"
wasm-bindgen-futures = { workspace = true }
config = "0.15.11"
ahash = { version = "0.8.12", default-features = false, features = ["no-rng"] }
ahash = { version = "0.8.12", default-features = false, features = [
"no-rng",
"std",
] }
vfs = "0.12"
hex = "0.4.3"
sha2 = "0.10.8"
Expand Down
8 changes: 8 additions & 0 deletions jans-cedarling/cedarling/src/bootstrap_config/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::collections::{HashMap, HashSet};
use std::env;
use std::fmt::Display;
use std::fs;
use std::num::NonZeroUsize;
use std::path::Path;
use std::str::FromStr;

Expand All @@ -20,6 +21,7 @@ use super::{
MemoryLogConfig, PolicyStoreConfig, PolicyStoreSource,
};
use super::{BootstrapConfigRaw, LockServiceConfig};
use crate::jwt_config::{TrustedIssuerLoaderConfig, TrustedIssuerLoaderTypeRaw};
use crate::log::{LogLevel, StdOutLoggerMode};
use jsonwebtoken::Algorithm;
use serde::{Deserialize, Deserializer, Serialize};
Expand Down Expand Up @@ -112,6 +114,9 @@ impl BootstrapConfig {
})
.transpose()?;

let trusted_issuer_workers = NonZeroUsize::new(raw.trusted_issuer_loader_workers)
.unwrap_or_else(|| NonZeroUsize::MIN);

// JWT Config
let jwt_config = JwtConfig {
jwks,
Expand All @@ -121,6 +126,9 @@ impl BootstrapConfig {
token_cache_max_ttl_secs: raw.token_cache_max_ttl,
token_cache_capacity: raw.token_cache_capacity,
token_cache_earliest_expiration_eviction: raw.token_cache_earliest_expiration_eviction,
trusted_issuer_loader: raw
.trusted_issuer_loader_type
.to_config(trusted_issuer_workers),
};

let authorization_config = AuthorizationConfig {
Expand Down
62 changes: 61 additions & 1 deletion jans-cedarling/cedarling/src/bootstrap_config/jwt_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use jsonwebtoken::Algorithm;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::{collections::HashSet, num::NonZeroUsize};

/// The set of Bootstrap properties related to JWT validation.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -52,6 +52,8 @@ pub struct JwtConfig {
/// When the cache reaches its capacity, the entry with the nearest
/// expiration timestamp will be removed to make room for a new one.
pub token_cache_earliest_expiration_eviction: bool,
/// Configuration for loading trusted issuers.
pub trusted_issuer_loader: TrustedIssuerLoaderConfig,
}

/// Validation options related to JSON Web Tokens (JWT).
Expand Down Expand Up @@ -182,6 +184,7 @@ impl Default for JwtConfig {
token_cache_capacity: 100,
token_cache_earliest_expiration_eviction: true,
token_cache_max_ttl_secs: 60 * 5, // 5min
trusted_issuer_loader: TrustedIssuerLoaderConfig::default(),
}
}
}
Expand Down Expand Up @@ -220,3 +223,60 @@ impl JwtConfig {
self
}
}

/// Raw representation of trusted issuer loader type from environment variable.
/// Is used in [`BootstrapConfigRaw`].
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub enum TrustedIssuerLoaderTypeRaw {
/// Synchronous loading
#[default]
#[serde(rename = "SYNC")]
Sync,
/// Asynchronous loading
#[serde(rename = "ASYNC")]
Async,
}

impl TrustedIssuerLoaderTypeRaw {
/// Converts raw representation to `TrustedIssuerLoaderConfig`.
pub(crate) fn to_config(&self, workers: NonZeroUsize) -> TrustedIssuerLoaderConfig {
match self {
TrustedIssuerLoaderTypeRaw::Sync => TrustedIssuerLoaderConfig::Sync { workers },
TrustedIssuerLoaderTypeRaw::Async => TrustedIssuerLoaderConfig::Async { workers },
}
}
}

/// Config structure that define how trusted issuers will be loaded.
///
/// Default is `Sync` with 1 worker.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TrustedIssuerLoaderConfig {
/// Synchronous loading, on start program.
/// The Cedarling will load all entities on start in "blocking mode" (you need to wait).
Sync {
/// Workers count
workers: NonZeroUsize,
},
/// Asynchronous loading, on start program.
/// The Cedarling will load all entities on the background.
/// You need specify workers count.
Async {
/// Workers count
workers: NonZeroUsize,
},
}

impl TrustedIssuerLoaderConfig {
pub(crate) fn is_sync(&self) -> bool {
matches!(self, TrustedIssuerLoaderConfig::Sync { .. })
}
}

impl Default for TrustedIssuerLoaderConfig {
fn default() -> Self {
Self::Sync {
workers: NonZeroUsize::new(1).unwrap(),
}
}
}
110 changes: 110 additions & 0 deletions jans-cedarling/cedarling/src/bootstrap_config/raw_config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::feature_types::{FeatureToggle, LoggerType};
use super::json_util::{deserialize_or_parse_string_as_json, parse_option_string};
use crate::UnsignedRoleIdSrc;
use crate::common::json_rules::JsonRule;
use crate::jwt_config::TrustedIssuerLoaderTypeRaw;
use crate::log::LogLevel;
use jsonwebtoken::Algorithm;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -305,6 +306,28 @@ pub struct BootstrapConfigRaw {
default = "default_true"
)]
pub token_cache_earliest_expiration_eviction: bool,
/// Type of trusted issuer loader.
/// If not set, synchronous loader is used.
/// Can be `SYNC` or `ASYNC`.
///
/// Sync loader means that trusted issuers will be loaded on initialization.
/// Async loader means that trusted issuers will be loaded in background.
#[serde(
rename = "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE",
default,
deserialize_with = "deserialize_or_parse_string_as_json"
)]
pub trusted_issuer_loader_type: TrustedIssuerLoaderTypeRaw,
/// Number of concurrent workers to use when loading trusted issuers.
/// Applies to both SYNC (parallel loading during initialization) and ASYNC (parallel background loading) modes.
/// Default value is 1.
/// Zero will be treated as default value.
#[serde(
rename = "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS",
default,
deserialize_with = "deserialize_or_parse_string_as_json"
)]
pub trusted_issuer_loader_workers: usize,
}

impl Default for BootstrapConfigRaw {
Expand Down Expand Up @@ -551,4 +574,91 @@ mod tests {
},
);
}

/// Tests default values for trusted issuer loader fields when no env vars or JSON.
#[test]
fn test_trusted_issuer_loader_defaults() {
with_env_vars(&[], || {
let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();

assert_eq!(
config.trusted_issuer_loader_type,
TrustedIssuerLoaderTypeRaw::Sync,
"Default loader type should be Sync"
);
assert_eq!(
config.trusted_issuer_loader_workers, 0,
"Default worker count should be 0 (will be treated as 1 later)"
);
});
}

/// Tests that environment variables for trusted issuer loader are parsed correctly.
#[test]
fn test_trusted_issuer_loader_env_vars() {
with_env_vars(
&[
("CEDARLING_TRUSTED_ISSUER_LOADER_TYPE", "ASYNC"),
("CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS", "5"),
],
|| {
let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();

assert_eq!(
config.trusted_issuer_loader_type,
TrustedIssuerLoaderTypeRaw::Async,
"Loader type should be Async from env var"
);
assert_eq!(
config.trusted_issuer_loader_workers, 5,
"Worker count should be 5 from env var"
);
},
);
}

/// Tests JSON deserialization for trusted issuer loader fields.
#[test]
fn test_trusted_issuer_loader_json_deserialization() {
// Valid JSON values
let valid_cases = vec![
(
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "SYNC", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": 3}"#,
TrustedIssuerLoaderTypeRaw::Sync,
3,
),
(
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "ASYNC", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": 1}"#,
TrustedIssuerLoaderTypeRaw::Async,
1,
),
];

for (json, expected_type, expected_workers) in valid_cases {
let config: BootstrapConfigRaw = serde_json::from_str(json).unwrap();
assert_eq!(
config.trusted_issuer_loader_type, expected_type,
"Loader type mismatch for JSON: {}",
json
);
assert_eq!(
config.trusted_issuer_loader_workers, expected_workers,
"Worker count mismatch for JSON: {}",
json
);
}

// Invalid JSON values should produce errors
let invalid_cases = vec![
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "INVALID"}"#,
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": -1}"#,
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": "not_a_number"}"#,
r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": 123}"#,
];

for json in invalid_cases {
let result: Result<BootstrapConfigRaw, _> = serde_json::from_str(json);
result.expect_err(&format!("Should fail to parse invalid JSON: {}", json));
}
}
}
Loading
Loading