mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 23:47:28 +00:00
fix(oidc): support separate discovery issuer (#5149)
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
// OIDC configuration field keys (used in KVS)
|
// OIDC configuration field keys (used in KVS)
|
||||||
pub const OIDC_CONFIG_URL: &str = "config_url";
|
pub const OIDC_CONFIG_URL: &str = "config_url";
|
||||||
|
pub const OIDC_ISSUER: &str = "issuer";
|
||||||
pub const OIDC_CLIENT_ID: &str = "client_id";
|
pub const OIDC_CLIENT_ID: &str = "client_id";
|
||||||
pub const OIDC_CLIENT_SECRET: &str = "client_secret";
|
pub const OIDC_CLIENT_SECRET: &str = "client_secret";
|
||||||
pub const OIDC_SCOPES: &str = "scopes";
|
pub const OIDC_SCOPES: &str = "scopes";
|
||||||
@@ -33,6 +34,7 @@ pub const OIDC_HIDE_FROM_UI: &str = "hide_from_ui";
|
|||||||
// Environment variable names for OIDC
|
// Environment variable names for OIDC
|
||||||
pub const ENV_IDENTITY_OPENID_ENABLE: &str = "RUSTFS_IDENTITY_OPENID_ENABLE";
|
pub const ENV_IDENTITY_OPENID_ENABLE: &str = "RUSTFS_IDENTITY_OPENID_ENABLE";
|
||||||
pub const ENV_IDENTITY_OPENID_CONFIG_URL: &str = "RUSTFS_IDENTITY_OPENID_CONFIG_URL";
|
pub const ENV_IDENTITY_OPENID_CONFIG_URL: &str = "RUSTFS_IDENTITY_OPENID_CONFIG_URL";
|
||||||
|
pub const ENV_IDENTITY_OPENID_ISSUER: &str = "RUSTFS_IDENTITY_OPENID_ISSUER";
|
||||||
pub const ENV_IDENTITY_OPENID_CLIENT_ID: &str = "RUSTFS_IDENTITY_OPENID_CLIENT_ID";
|
pub const ENV_IDENTITY_OPENID_CLIENT_ID: &str = "RUSTFS_IDENTITY_OPENID_CLIENT_ID";
|
||||||
pub const ENV_IDENTITY_OPENID_CLIENT_SECRET: &str = "RUSTFS_IDENTITY_OPENID_CLIENT_SECRET";
|
pub const ENV_IDENTITY_OPENID_CLIENT_SECRET: &str = "RUSTFS_IDENTITY_OPENID_CLIENT_SECRET";
|
||||||
pub const ENV_IDENTITY_OPENID_SCOPES: &str = "RUSTFS_IDENTITY_OPENID_SCOPES";
|
pub const ENV_IDENTITY_OPENID_SCOPES: &str = "RUSTFS_IDENTITY_OPENID_SCOPES";
|
||||||
@@ -50,9 +52,10 @@ pub const ENV_IDENTITY_OPENID_USERNAME_CLAIM: &str = "RUSTFS_IDENTITY_OPENID_USE
|
|||||||
pub const ENV_IDENTITY_OPENID_HIDE_FROM_UI: &str = "RUSTFS_IDENTITY_OPENID_HIDE_FROM_UI";
|
pub const ENV_IDENTITY_OPENID_HIDE_FROM_UI: &str = "RUSTFS_IDENTITY_OPENID_HIDE_FROM_UI";
|
||||||
|
|
||||||
/// List of all environment variable keys for an OIDC provider.
|
/// List of all environment variable keys for an OIDC provider.
|
||||||
pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 17] = &[
|
pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 18] = &[
|
||||||
ENV_IDENTITY_OPENID_ENABLE,
|
ENV_IDENTITY_OPENID_ENABLE,
|
||||||
ENV_IDENTITY_OPENID_CONFIG_URL,
|
ENV_IDENTITY_OPENID_CONFIG_URL,
|
||||||
|
ENV_IDENTITY_OPENID_ISSUER,
|
||||||
ENV_IDENTITY_OPENID_CLIENT_ID,
|
ENV_IDENTITY_OPENID_CLIENT_ID,
|
||||||
ENV_IDENTITY_OPENID_CLIENT_SECRET,
|
ENV_IDENTITY_OPENID_CLIENT_SECRET,
|
||||||
ENV_IDENTITY_OPENID_SCOPES,
|
ENV_IDENTITY_OPENID_SCOPES,
|
||||||
@@ -74,6 +77,7 @@ pub const ENV_IDENTITY_OPENID_KEYS: &[&str; 17] = &[
|
|||||||
pub const IDENTITY_OPENID_KEYS: &[&str] = &[
|
pub const IDENTITY_OPENID_KEYS: &[&str] = &[
|
||||||
crate::ENABLE_KEY,
|
crate::ENABLE_KEY,
|
||||||
OIDC_CONFIG_URL,
|
OIDC_CONFIG_URL,
|
||||||
|
OIDC_ISSUER,
|
||||||
OIDC_CLIENT_ID,
|
OIDC_CLIENT_ID,
|
||||||
OIDC_CLIENT_SECRET,
|
OIDC_CLIENT_SECRET,
|
||||||
OIDC_SCOPES,
|
OIDC_SCOPES,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use rustfs_config::{
|
|||||||
oidc::{
|
oidc::{
|
||||||
OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DEFAULT_CLAIM_NAME,
|
OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DEFAULT_CLAIM_NAME,
|
||||||
OIDC_DEFAULT_EMAIL_CLAIM, OIDC_DEFAULT_GROUPS_CLAIM, OIDC_DEFAULT_ROLES_CLAIM, OIDC_DEFAULT_SCOPES,
|
OIDC_DEFAULT_EMAIL_CLAIM, OIDC_DEFAULT_GROUPS_CLAIM, OIDC_DEFAULT_ROLES_CLAIM, OIDC_DEFAULT_SCOPES,
|
||||||
OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_OTHER_AUDIENCES,
|
OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_ISSUER, OIDC_OTHER_AUDIENCES,
|
||||||
OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM,
|
OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -37,6 +37,11 @@ pub static DEFAULT_IDENTITY_OPENID_KVS: LazyLock<KVS> = LazyLock::new(|| {
|
|||||||
value: "".to_owned(),
|
value: "".to_owned(),
|
||||||
hidden_if_empty: false,
|
hidden_if_empty: false,
|
||||||
},
|
},
|
||||||
|
KV {
|
||||||
|
key: OIDC_ISSUER.to_owned(),
|
||||||
|
value: "".to_owned(),
|
||||||
|
hidden_if_empty: false,
|
||||||
|
},
|
||||||
KV {
|
KV {
|
||||||
key: OIDC_CLIENT_ID.to_owned(),
|
key: OIDC_CLIENT_ID.to_owned(),
|
||||||
value: "".to_owned(),
|
value: "".to_owned(),
|
||||||
|
|||||||
+158
-1
@@ -19,7 +19,7 @@
|
|||||||
//! and ID token verification.
|
//! and ID token verification.
|
||||||
|
|
||||||
use crate::oidc_state::{OidcAuthSession, OidcLogoutSession, OidcStateStore};
|
use crate::oidc_state::{OidcAuthSession, OidcLogoutSession, OidcStateStore};
|
||||||
use openidconnect::core::{CoreAuthenticationFlow, CoreClient, CoreIdToken};
|
use openidconnect::core::{CoreAuthenticationFlow, CoreClient, CoreIdToken, CoreJsonWebKeySet};
|
||||||
use openidconnect::{
|
use openidconnect::{
|
||||||
AsyncHttpClient, Audience, AuthType, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, LogoutRequest, Nonce,
|
AsyncHttpClient, Audience, AuthType, AuthorizationCode, ClientId, ClientSecret, CsrfToken, IssuerUrl, LogoutRequest, Nonce,
|
||||||
PkceCodeChallenge, PkceCodeVerifier, PostLogoutRedirectUrl, ProviderMetadataWithLogout, RedirectUrl, RequestTokenError,
|
PkceCodeChallenge, PkceCodeVerifier, PostLogoutRedirectUrl, ProviderMetadataWithLogout, RedirectUrl, RequestTokenError,
|
||||||
@@ -436,6 +436,7 @@ pub struct OidcProviderConfig {
|
|||||||
pub id: String,
|
pub id: String,
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub config_url: String,
|
pub config_url: String,
|
||||||
|
pub issuer: Option<String>,
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
pub client_secret: Option<String>,
|
pub client_secret: Option<String>,
|
||||||
pub scopes: Vec<String>,
|
pub scopes: Vec<String>,
|
||||||
@@ -459,6 +460,7 @@ impl fmt::Debug for OidcProviderConfig {
|
|||||||
.field("id", &self.id)
|
.field("id", &self.id)
|
||||||
.field("enabled", &self.enabled)
|
.field("enabled", &self.enabled)
|
||||||
.field("config_url", &self.config_url)
|
.field("config_url", &self.config_url)
|
||||||
|
.field("issuer", &self.issuer)
|
||||||
.field("client_id", &self.client_id)
|
.field("client_id", &self.client_id)
|
||||||
.field("client_secret", &redacted_optional_secret(self.client_secret.as_deref()))
|
.field("client_secret", &redacted_optional_secret(self.client_secret.as_deref()))
|
||||||
.field("scopes", &self.scopes)
|
.field("scopes", &self.scopes)
|
||||||
@@ -1409,6 +1411,7 @@ impl OidcSys {
|
|||||||
|
|
||||||
let enable_val = get_env(ENV_IDENTITY_OPENID_ENABLE);
|
let enable_val = get_env(ENV_IDENTITY_OPENID_ENABLE);
|
||||||
let config_url = get_env(ENV_IDENTITY_OPENID_CONFIG_URL);
|
let config_url = get_env(ENV_IDENTITY_OPENID_CONFIG_URL);
|
||||||
|
let issuer = get_env(ENV_IDENTITY_OPENID_ISSUER);
|
||||||
|
|
||||||
// Skip if no config URL
|
// Skip if no config URL
|
||||||
if config_url.is_empty() {
|
if config_url.is_empty() {
|
||||||
@@ -1485,6 +1488,7 @@ impl OidcSys {
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
enabled,
|
enabled,
|
||||||
config_url,
|
config_url,
|
||||||
|
issuer: if issuer.is_empty() { None } else { Some(issuer) },
|
||||||
client_id: get_env(ENV_IDENTITY_OPENID_CLIENT_ID),
|
client_id: get_env(ENV_IDENTITY_OPENID_CLIENT_ID),
|
||||||
client_secret,
|
client_secret,
|
||||||
scopes,
|
scopes,
|
||||||
@@ -1553,6 +1557,7 @@ impl OidcSys {
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
enabled,
|
enabled,
|
||||||
config_url,
|
config_url,
|
||||||
|
issuer: kvs.lookup(OIDC_ISSUER).filter(|v| !v.is_empty()),
|
||||||
client_id: kvs.get(OIDC_CLIENT_ID),
|
client_id: kvs.get(OIDC_CLIENT_ID),
|
||||||
client_secret,
|
client_secret,
|
||||||
scopes,
|
scopes,
|
||||||
@@ -1574,6 +1579,10 @@ impl OidcSys {
|
|||||||
/// Perform OIDC discovery for a provider.
|
/// Perform OIDC discovery for a provider.
|
||||||
/// `discover_async` fetches the discovery document and JWKS in one step.
|
/// `discover_async` fetches the discovery document and JWKS in one step.
|
||||||
async fn discover_provider(config: &OidcProviderConfig, http_client: &ReqwestHttpClient) -> Result<ProviderState, String> {
|
async fn discover_provider(config: &OidcProviderConfig, http_client: &ReqwestHttpClient) -> Result<ProviderState, String> {
|
||||||
|
if let Some(issuer) = config.issuer.as_deref().filter(|issuer| !issuer.trim().is_empty()) {
|
||||||
|
return Self::discover_provider_from_config_url(config, issuer, http_client).await;
|
||||||
|
}
|
||||||
|
|
||||||
// The openidconnect crate expects the issuer URL (base), not the
|
// The openidconnect crate expects the issuer URL (base), not the
|
||||||
// .well-known/openid-configuration URL.
|
// .well-known/openid-configuration URL.
|
||||||
let base_issuer = normalize_config_url(&config.config_url)?;
|
let base_issuer = normalize_config_url(&config.config_url)?;
|
||||||
@@ -1641,6 +1650,48 @@ impl OidcSys {
|
|||||||
last_errors.join("; ")
|
last_errors.join("; ")
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn discover_provider_from_config_url(
|
||||||
|
config: &OidcProviderConfig,
|
||||||
|
issuer: &str,
|
||||||
|
http_client: &ReqwestHttpClient,
|
||||||
|
) -> Result<ProviderState, String> {
|
||||||
|
let issuer_url = IssuerUrl::new(issuer.trim().to_string()).map_err(|e| format!("invalid issuer URL: {e}"))?;
|
||||||
|
let discovery_url = discovery_url_from_config_url(&config.config_url)?;
|
||||||
|
let request = http::Request::builder()
|
||||||
|
.uri(discovery_url.to_string())
|
||||||
|
.method(http::Method::GET)
|
||||||
|
.header(http::header::ACCEPT, "application/json")
|
||||||
|
.body(Vec::new())
|
||||||
|
.map_err(|err| format!("failed to prepare discovery request: {err}"))?;
|
||||||
|
|
||||||
|
let response = http_client
|
||||||
|
.call(request)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("discovery request failed: {err}"))?;
|
||||||
|
if response.status() != http::StatusCode::OK {
|
||||||
|
return Err(format!("discovery failed: HTTP status code {} at {}", response.status(), discovery_url));
|
||||||
|
}
|
||||||
|
|
||||||
|
let provider_metadata = serde_json::from_slice::<ProviderMetadataWithLogout>(response.body())
|
||||||
|
.map_err(|err| format!("failed to parse discovery response: {err}"))?;
|
||||||
|
if provider_metadata.issuer() != &issuer_url {
|
||||||
|
return Err(format!(
|
||||||
|
"unexpected issuer URI `{}` (expected `{}`)",
|
||||||
|
provider_metadata.issuer().as_str(),
|
||||||
|
issuer_url.as_str()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let jwks = CoreJsonWebKeySet::fetch_async(provider_metadata.jwks_uri(), http_client)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("failed to fetch JWKS: {err}"))?;
|
||||||
|
|
||||||
|
Ok(ProviderState {
|
||||||
|
metadata: provider_metadata.set_jwks(jwks),
|
||||||
|
discovered_at: Instant::now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_oidc_provider_configs_from_env() -> Vec<OidcProviderConfig> {
|
pub fn load_oidc_provider_configs_from_env() -> Vec<OidcProviderConfig> {
|
||||||
@@ -1754,6 +1805,34 @@ fn normalize_config_url(config_url: &str) -> Result<String, String> {
|
|||||||
Ok(issuer)
|
Ok(issuer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn discovery_url_from_config_url(config_url: &str) -> Result<Url, String> {
|
||||||
|
let mut url = Url::parse(config_url.trim()).map_err(|e| format!("invalid config_url: {e}"))?;
|
||||||
|
if url.scheme() != "http" && url.scheme() != "https" {
|
||||||
|
return Err(format!("invalid config_url scheme: {}", url.scheme()));
|
||||||
|
}
|
||||||
|
if url.host_str().is_none() {
|
||||||
|
return Err("config_url missing host".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = url.path().to_string();
|
||||||
|
let without_trailing_slash = path.strip_suffix('/').unwrap_or(&path);
|
||||||
|
if without_trailing_slash.ends_with("/.well-known/openid-configuration") {
|
||||||
|
url.set_path(without_trailing_slash);
|
||||||
|
return Ok(url);
|
||||||
|
}
|
||||||
|
if without_trailing_slash.contains("/.well-known/") {
|
||||||
|
return Err("config_url uses an unsupported .well-known discovery URL".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let discovery_path = if without_trailing_slash.is_empty() || without_trailing_slash == "/" {
|
||||||
|
"/.well-known/openid-configuration".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{without_trailing_slash}/.well-known/openid-configuration")
|
||||||
|
};
|
||||||
|
url.set_path(&discovery_path);
|
||||||
|
Ok(url)
|
||||||
|
}
|
||||||
|
|
||||||
fn issuer_candidates(base: &str) -> Vec<String> {
|
fn issuer_candidates(base: &str) -> Vec<String> {
|
||||||
let original = base.trim();
|
let original = base.trim();
|
||||||
let mut variants = Vec::with_capacity(2);
|
let mut variants = Vec::with_capacity(2);
|
||||||
@@ -2082,6 +2161,23 @@ mod tests {
|
|||||||
assert!(normalize_config_url("not-a-url").is_err());
|
assert!(normalize_config_url("not-a-url").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_discovery_url_from_config_url() {
|
||||||
|
assert_eq!(
|
||||||
|
discovery_url_from_config_url("https://idp.example.com/.well-known/openid-configuration")
|
||||||
|
.expect("config URL should parse")
|
||||||
|
.as_str(),
|
||||||
|
"https://idp.example.com/.well-known/openid-configuration"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
discovery_url_from_config_url("https://idp.example.com/realms/app")
|
||||||
|
.expect("issuer URL should derive discovery URL")
|
||||||
|
.as_str(),
|
||||||
|
"https://idp.example.com/realms/app/.well-known/openid-configuration"
|
||||||
|
);
|
||||||
|
assert!(discovery_url_from_config_url("https://idp.example.com/.well-known/not-openid").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_issuer_candidates() {
|
fn test_issuer_candidates() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2109,6 +2205,7 @@ mod tests {
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: config_url.to_string(),
|
config_url: config_url.to_string(),
|
||||||
|
issuer: None,
|
||||||
client_id: "rustfs-oidc-test".to_string(),
|
client_id: "rustfs-oidc-test".to_string(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: vec!["openid".to_string()],
|
scopes: vec!["openid".to_string()],
|
||||||
@@ -2289,6 +2386,43 @@ mod tests {
|
|||||||
assert!(handle.join().is_ok());
|
assert!(handle.join().is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_validate_oidc_provider_config_accepts_separate_issuer() {
|
||||||
|
let Some((base, handle)) = start_mock_oidc_discovery_server(|_| "https://public.example.com/realms/app".to_string(), 2)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut config =
|
||||||
|
build_mocked_oidc_provider_config("default", &format!("{base}/internal/realms/app/.well-known/openid-configuration"));
|
||||||
|
config.issuer = Some("https://public.example.com/realms/app".to_string());
|
||||||
|
|
||||||
|
let validation_result = validate_mocked_oidc_provider_config(&config)
|
||||||
|
.await
|
||||||
|
.expect("OIDC provider validation should succeed");
|
||||||
|
|
||||||
|
assert_eq!(validation_result.issuer, "https://public.example.com/realms/app");
|
||||||
|
assert!(handle.join().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_validate_oidc_provider_config_rejects_separate_issuer_mismatch() {
|
||||||
|
let Some((base, handle)) = start_mock_oidc_discovery_server(|_| "https://public.example.com/realms/other".to_string(), 1)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut config =
|
||||||
|
build_mocked_oidc_provider_config("default", &format!("{base}/internal/realms/app/.well-known/openid-configuration"));
|
||||||
|
config.issuer = Some("https://public.example.com/realms/app".to_string());
|
||||||
|
|
||||||
|
let err = validate_mocked_oidc_provider_config(&config)
|
||||||
|
.await
|
||||||
|
.expect_err("OIDC provider validation should fail");
|
||||||
|
|
||||||
|
assert!(err.contains("unexpected issuer URI"));
|
||||||
|
assert!(err.contains("https://public.example.com/realms/app"));
|
||||||
|
assert!(handle.join().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_validate_oidc_provider_config_returns_detailed_errors() {
|
async fn test_validate_oidc_provider_config_returns_detailed_errors() {
|
||||||
let Some((base, handle)) = start_mock_oidc_discovery_server(|base| format!("{base}/application/o/other"), 8) else {
|
let Some((base, handle)) = start_mock_oidc_discovery_server(|base| format!("{base}/application/o/other"), 8) else {
|
||||||
@@ -2399,6 +2533,25 @@ mod tests {
|
|||||||
assert!(config.is_none());
|
assert!(config.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_single_provider_reads_issuer() {
|
||||||
|
temp_env::with_vars(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ENV_IDENTITY_OPENID_CONFIG_URL,
|
||||||
|
Some("http://keycloak.ns.svc.cluster.local:8080/realms/app/.well-known/openid-configuration"),
|
||||||
|
),
|
||||||
|
(ENV_IDENTITY_OPENID_ISSUER, Some("https://app.local/realms/app")),
|
||||||
|
(ENV_IDENTITY_OPENID_CLIENT_ID, Some("console")),
|
||||||
|
],
|
||||||
|
|| {
|
||||||
|
let config = OidcSys::parse_single_provider("", "default").expect("provider config should parse");
|
||||||
|
|
||||||
|
assert_eq!(config.issuer.as_deref(), Some("https://app.local/realms/app"));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_persisted_provider_config() {
|
fn test_parse_persisted_provider_config() {
|
||||||
let mut cfg = ServerConfig::new();
|
let mut cfg = ServerConfig::new();
|
||||||
@@ -2425,6 +2578,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
kvs.insert(OIDC_CLIENT_ID.to_string(), "console".to_string());
|
kvs.insert(OIDC_CLIENT_ID.to_string(), "console".to_string());
|
||||||
kvs.insert(ENABLE_KEY.to_string(), EnableState::On.to_string());
|
kvs.insert(ENABLE_KEY.to_string(), EnableState::On.to_string());
|
||||||
|
kvs.insert(OIDC_ISSUER.to_string(), "https://issuer.example".to_string());
|
||||||
kvs.insert(OIDC_ROLES_CLAIM.to_string(), "app_roles".to_string());
|
kvs.insert(OIDC_ROLES_CLAIM.to_string(), "app_roles".to_string());
|
||||||
|
|
||||||
cfg.0
|
cfg.0
|
||||||
@@ -2436,6 +2590,7 @@ mod tests {
|
|||||||
assert_eq!(parsed.len(), 1);
|
assert_eq!(parsed.len(), 1);
|
||||||
assert_eq!(parsed[0].id, "default");
|
assert_eq!(parsed[0].id, "default");
|
||||||
assert_eq!(parsed[0].client_id, "console");
|
assert_eq!(parsed[0].client_id, "console");
|
||||||
|
assert_eq!(parsed[0].issuer.as_deref(), Some("https://issuer.example"));
|
||||||
assert!(parsed[0].enabled);
|
assert!(parsed[0].enabled);
|
||||||
assert_eq!(parsed[0].roles_claim, "app_roles");
|
assert_eq!(parsed[0].roles_claim, "app_roles");
|
||||||
}
|
}
|
||||||
@@ -2528,6 +2683,7 @@ mod tests {
|
|||||||
id: id.to_string(),
|
id: id.to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: format!("https://example.com/{id}/.well-known/openid-configuration"),
|
config_url: format!("https://example.com/{id}/.well-known/openid-configuration"),
|
||||||
|
issuer: None,
|
||||||
client_id: "client-id".to_string(),
|
client_id: "client-id".to_string(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: vec!["openid".to_string()],
|
scopes: vec!["openid".to_string()],
|
||||||
@@ -2830,6 +2986,7 @@ mod tests {
|
|||||||
id: "test".to_string(),
|
id: "test".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
||||||
|
issuer: None,
|
||||||
client_id: "my-client".to_string(),
|
client_id: "my-client".to_string(),
|
||||||
client_secret: Some("secret".to_string()),
|
client_secret: Some("secret".to_string()),
|
||||||
scopes: vec!["openid".to_string(), "profile".to_string(), "email".to_string()],
|
scopes: vec!["openid".to_string(), "profile".to_string(), "email".to_string()],
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ const EXTERNAL_COMPATIBLE_SUFFIXES: &[&str] = &[
|
|||||||
"IDENTITY_OPENID_CLIENT_SECRET",
|
"IDENTITY_OPENID_CLIENT_SECRET",
|
||||||
"IDENTITY_OPENID_CONFIG_URL",
|
"IDENTITY_OPENID_CONFIG_URL",
|
||||||
"IDENTITY_OPENID_DISPLAY_NAME",
|
"IDENTITY_OPENID_DISPLAY_NAME",
|
||||||
|
"IDENTITY_OPENID_ISSUER",
|
||||||
"IDENTITY_OPENID_REDIRECT_URI",
|
"IDENTITY_OPENID_REDIRECT_URI",
|
||||||
"IDENTITY_OPENID_SCOPES",
|
"IDENTITY_OPENID_SCOPES",
|
||||||
"ILM_EXPIRATION_WORKERS",
|
"ILM_EXPIRATION_WORKERS",
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ use rustfs_config::notify::{
|
|||||||
use rustfs_config::oidc::{
|
use rustfs_config::oidc::{
|
||||||
ENV_IDENTITY_OPENID_CLAIM_NAME, ENV_IDENTITY_OPENID_CLAIM_PREFIX, ENV_IDENTITY_OPENID_CLIENT_ID,
|
ENV_IDENTITY_OPENID_CLAIM_NAME, ENV_IDENTITY_OPENID_CLAIM_PREFIX, ENV_IDENTITY_OPENID_CLIENT_ID,
|
||||||
ENV_IDENTITY_OPENID_CLIENT_SECRET, ENV_IDENTITY_OPENID_CONFIG_URL, ENV_IDENTITY_OPENID_DISPLAY_NAME,
|
ENV_IDENTITY_OPENID_CLIENT_SECRET, ENV_IDENTITY_OPENID_CONFIG_URL, ENV_IDENTITY_OPENID_DISPLAY_NAME,
|
||||||
ENV_IDENTITY_OPENID_EMAIL_CLAIM, ENV_IDENTITY_OPENID_ENABLE, ENV_IDENTITY_OPENID_GROUPS_CLAIM,
|
ENV_IDENTITY_OPENID_EMAIL_CLAIM, ENV_IDENTITY_OPENID_ENABLE, ENV_IDENTITY_OPENID_GROUPS_CLAIM, ENV_IDENTITY_OPENID_ISSUER,
|
||||||
ENV_IDENTITY_OPENID_REDIRECT_URI, ENV_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC, ENV_IDENTITY_OPENID_ROLE_POLICY,
|
ENV_IDENTITY_OPENID_REDIRECT_URI, ENV_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC, ENV_IDENTITY_OPENID_ROLE_POLICY,
|
||||||
ENV_IDENTITY_OPENID_SCOPES, ENV_IDENTITY_OPENID_USERNAME_CLAIM, IDENTITY_OPENID_SUB_SYS, OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX,
|
ENV_IDENTITY_OPENID_SCOPES, ENV_IDENTITY_OPENID_USERNAME_CLAIM, IDENTITY_OPENID_SUB_SYS, OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX,
|
||||||
OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM,
|
OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_ISSUER,
|
||||||
OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_SCOPES, OIDC_USERNAME_CLAIM,
|
OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_SCOPES, OIDC_USERNAME_CLAIM,
|
||||||
};
|
};
|
||||||
use rustfs_config::server_config::{Config as ServerConfig, DEFAULT_KVS, KV, KVS};
|
use rustfs_config::server_config::{Config as ServerConfig, DEFAULT_KVS, KV, KVS};
|
||||||
@@ -313,6 +313,12 @@ const OIDC_HELP_KEYS: &[HelpKeyMetadata] = &[
|
|||||||
description: "openid discovery document URL e.g. \"https://accounts.google.com/.well-known/openid-configuration\"",
|
description: "openid discovery document URL e.g. \"https://accounts.google.com/.well-known/openid-configuration\"",
|
||||||
optional: false,
|
optional: false,
|
||||||
},
|
},
|
||||||
|
HelpKeyMetadata {
|
||||||
|
key: OIDC_ISSUER,
|
||||||
|
type_name: "url",
|
||||||
|
description: "expected OpenID issuer URL when it differs from config_url",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
HelpKeyMetadata {
|
HelpKeyMetadata {
|
||||||
key: OIDC_CLIENT_ID,
|
key: OIDC_CLIENT_ID,
|
||||||
type_name: "string",
|
type_name: "string",
|
||||||
@@ -1374,6 +1380,7 @@ fn env_help_key(sub_system: &str, key: &str) -> String {
|
|||||||
(HEAL_SUB_SYS, HEAL_BITROT_CYCLE) => ENV_SCANNER_BITROT_CYCLE_SECS.to_string(),
|
(HEAL_SUB_SYS, HEAL_BITROT_CYCLE) => ENV_SCANNER_BITROT_CYCLE_SECS.to_string(),
|
||||||
(IDENTITY_OPENID_SUB_SYS, ENABLE_KEY) => ENV_IDENTITY_OPENID_ENABLE.to_string(),
|
(IDENTITY_OPENID_SUB_SYS, ENABLE_KEY) => ENV_IDENTITY_OPENID_ENABLE.to_string(),
|
||||||
(IDENTITY_OPENID_SUB_SYS, OIDC_CONFIG_URL) => ENV_IDENTITY_OPENID_CONFIG_URL.to_string(),
|
(IDENTITY_OPENID_SUB_SYS, OIDC_CONFIG_URL) => ENV_IDENTITY_OPENID_CONFIG_URL.to_string(),
|
||||||
|
(IDENTITY_OPENID_SUB_SYS, OIDC_ISSUER) => ENV_IDENTITY_OPENID_ISSUER.to_string(),
|
||||||
(IDENTITY_OPENID_SUB_SYS, OIDC_CLIENT_ID) => ENV_IDENTITY_OPENID_CLIENT_ID.to_string(),
|
(IDENTITY_OPENID_SUB_SYS, OIDC_CLIENT_ID) => ENV_IDENTITY_OPENID_CLIENT_ID.to_string(),
|
||||||
(IDENTITY_OPENID_SUB_SYS, OIDC_CLIENT_SECRET) => ENV_IDENTITY_OPENID_CLIENT_SECRET.to_string(),
|
(IDENTITY_OPENID_SUB_SYS, OIDC_CLIENT_SECRET) => ENV_IDENTITY_OPENID_CLIENT_SECRET.to_string(),
|
||||||
(IDENTITY_OPENID_SUB_SYS, OIDC_SCOPES) => ENV_IDENTITY_OPENID_SCOPES.to_string(),
|
(IDENTITY_OPENID_SUB_SYS, OIDC_SCOPES) => ENV_IDENTITY_OPENID_SCOPES.to_string(),
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ use matchit::Params;
|
|||||||
use rustfs_config::oidc::{
|
use rustfs_config::oidc::{
|
||||||
IDENTITY_OPENID_SUB_SYS, OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL,
|
IDENTITY_OPENID_SUB_SYS, OIDC_CLAIM_NAME, OIDC_CLAIM_PREFIX, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_CONFIG_URL,
|
||||||
OIDC_DEFAULT_CLAIM_NAME, OIDC_DEFAULT_EMAIL_CLAIM, OIDC_DEFAULT_GROUPS_CLAIM, OIDC_DEFAULT_ROLES_CLAIM, OIDC_DEFAULT_SCOPES,
|
OIDC_DEFAULT_CLAIM_NAME, OIDC_DEFAULT_EMAIL_CLAIM, OIDC_DEFAULT_GROUPS_CLAIM, OIDC_DEFAULT_ROLES_CLAIM, OIDC_DEFAULT_SCOPES,
|
||||||
OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_HIDE_FROM_UI, OIDC_OTHER_AUDIENCES,
|
OIDC_DEFAULT_USERNAME_CLAIM, OIDC_DISPLAY_NAME, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM, OIDC_HIDE_FROM_UI, OIDC_ISSUER,
|
||||||
OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES, OIDC_USERNAME_CLAIM,
|
OIDC_OTHER_AUDIENCES, OIDC_REDIRECT_URI, OIDC_REDIRECT_URI_DYNAMIC, OIDC_ROLE_POLICY, OIDC_ROLES_CLAIM, OIDC_SCOPES,
|
||||||
|
OIDC_USERNAME_CLAIM,
|
||||||
};
|
};
|
||||||
use rustfs_config::server_config::Config as ServerConfig;
|
use rustfs_config::server_config::Config as ServerConfig;
|
||||||
use rustfs_config::{DEFAULT_DELIMITER, ENABLE_KEY, ENV_RUSTFS_BROWSER_REDIRECT_URL, EnableState, MAX_ADMIN_REQUEST_BODY_SIZE};
|
use rustfs_config::{DEFAULT_DELIMITER, ENABLE_KEY, ENV_RUSTFS_BROWSER_REDIRECT_URL, EnableState, MAX_ADMIN_REQUEST_BODY_SIZE};
|
||||||
@@ -164,6 +165,7 @@ struct OidcConfigView {
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
config_url: String,
|
config_url: String,
|
||||||
|
issuer: Option<String>,
|
||||||
client_id: String,
|
client_id: String,
|
||||||
client_secret_configured: bool,
|
client_secret_configured: bool,
|
||||||
scopes: Vec<String>,
|
scopes: Vec<String>,
|
||||||
@@ -202,6 +204,7 @@ struct OidcConfigUpsertRequest {
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
config_url: String,
|
config_url: String,
|
||||||
|
issuer: Option<String>,
|
||||||
client_id: String,
|
client_id: String,
|
||||||
client_secret: Option<String>,
|
client_secret: Option<String>,
|
||||||
scopes: Vec<String>,
|
scopes: Vec<String>,
|
||||||
@@ -224,6 +227,7 @@ impl Default for OidcConfigUpsertRequest {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
display_name: String::new(),
|
display_name: String::new(),
|
||||||
config_url: String::new(),
|
config_url: String::new(),
|
||||||
|
issuer: None,
|
||||||
client_id: String::new(),
|
client_id: String::new(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: OIDC_DEFAULT_SCOPES.split(',').map(ToString::to_string).collect(),
|
scopes: OIDC_DEFAULT_SCOPES.split(',').map(ToString::to_string).collect(),
|
||||||
@@ -249,6 +253,7 @@ struct OidcConfigValidateRequest {
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
display_name: String,
|
display_name: String,
|
||||||
config_url: String,
|
config_url: String,
|
||||||
|
issuer: Option<String>,
|
||||||
client_id: String,
|
client_id: String,
|
||||||
client_secret: Option<String>,
|
client_secret: Option<String>,
|
||||||
scopes: Vec<String>,
|
scopes: Vec<String>,
|
||||||
@@ -272,6 +277,7 @@ impl Default for OidcConfigValidateRequest {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
display_name: String::new(),
|
display_name: String::new(),
|
||||||
config_url: String::new(),
|
config_url: String::new(),
|
||||||
|
issuer: None,
|
||||||
client_id: String::new(),
|
client_id: String::new(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: OIDC_DEFAULT_SCOPES.split(',').map(ToString::to_string).collect(),
|
scopes: OIDC_DEFAULT_SCOPES.split(',').map(ToString::to_string).collect(),
|
||||||
@@ -328,6 +334,7 @@ impl Operation for GetOidcConfigHandler {
|
|||||||
enabled: provider.config.enabled,
|
enabled: provider.config.enabled,
|
||||||
display_name: provider.config.display_name.clone(),
|
display_name: provider.config.display_name.clone(),
|
||||||
config_url: provider.config.config_url.clone(),
|
config_url: provider.config.config_url.clone(),
|
||||||
|
issuer: provider.config.issuer.clone(),
|
||||||
client_id: provider.config.client_id.clone(),
|
client_id: provider.config.client_id.clone(),
|
||||||
client_secret_configured: provider.config.client_secret.is_some(),
|
client_secret_configured: provider.config.client_secret.is_some(),
|
||||||
scopes: provider.config.scopes.clone(),
|
scopes: provider.config.scopes.clone(),
|
||||||
@@ -990,6 +997,16 @@ fn validate_absolute_http_url(value: &str, field_name: &str) -> S3Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_absolute_http_url_without_outbound_check(value: &str, field_name: &str) -> S3Result<()> {
|
||||||
|
let parsed = Url::parse(value).map_err(|_| s3_error!(InvalidRequest, "{} must be an absolute http/https URL", field_name))?;
|
||||||
|
|
||||||
|
if !is_valid_scheme(parsed.scheme()) || parsed.host_str().is_none() {
|
||||||
|
return Err(s3_error!(InvalidRequest, "{} must be an absolute http/https URL", field_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_provider_config_fields(config: &rustfs_iam::oidc::OidcProviderConfig) -> S3Result<()> {
|
fn validate_provider_config_fields(config: &rustfs_iam::oidc::OidcProviderConfig) -> S3Result<()> {
|
||||||
if !is_valid_provider_id(&config.id) {
|
if !is_valid_provider_id(&config.id) {
|
||||||
return Err(s3_error!(InvalidRequest, "invalid provider_id"));
|
return Err(s3_error!(InvalidRequest, "invalid provider_id"));
|
||||||
@@ -998,6 +1015,9 @@ fn validate_provider_config_fields(config: &rustfs_iam::oidc::OidcProviderConfig
|
|||||||
return Err(s3_error!(InvalidRequest, "config_url is required"));
|
return Err(s3_error!(InvalidRequest, "config_url is required"));
|
||||||
}
|
}
|
||||||
validate_absolute_http_url(&config.config_url, "config_url")?;
|
validate_absolute_http_url(&config.config_url, "config_url")?;
|
||||||
|
if let Some(issuer) = config.issuer.as_deref() {
|
||||||
|
validate_absolute_http_url_without_outbound_check(issuer, "issuer")?;
|
||||||
|
}
|
||||||
|
|
||||||
if config.client_id.trim().is_empty() {
|
if config.client_id.trim().is_empty() {
|
||||||
return Err(s3_error!(InvalidRequest, "client_id is required"));
|
return Err(s3_error!(InvalidRequest, "client_id is required"));
|
||||||
@@ -1031,6 +1051,7 @@ fn or_default(value: &str, default: &str) -> String {
|
|||||||
/// Normalize an `OidcProviderConfig` by trimming strings and applying defaults.
|
/// Normalize an `OidcProviderConfig` by trimming strings and applying defaults.
|
||||||
fn normalize_provider_config(mut config: rustfs_iam::oidc::OidcProviderConfig) -> rustfs_iam::oidc::OidcProviderConfig {
|
fn normalize_provider_config(mut config: rustfs_iam::oidc::OidcProviderConfig) -> rustfs_iam::oidc::OidcProviderConfig {
|
||||||
config.config_url = config.config_url.trim().to_string();
|
config.config_url = config.config_url.trim().to_string();
|
||||||
|
config.issuer = normalize_optional(config.issuer);
|
||||||
config.client_id = config.client_id.trim().to_string();
|
config.client_id = config.client_id.trim().to_string();
|
||||||
config.scopes = normalize_scopes(&config.scopes);
|
config.scopes = normalize_scopes(&config.scopes);
|
||||||
config.redirect_uri = normalize_optional(config.redirect_uri);
|
config.redirect_uri = normalize_optional(config.redirect_uri);
|
||||||
@@ -1059,6 +1080,7 @@ fn build_provider_config_from_upsert(
|
|||||||
id: provider_id.to_string(),
|
id: provider_id.to_string(),
|
||||||
enabled: request.enabled,
|
enabled: request.enabled,
|
||||||
config_url: request.config_url,
|
config_url: request.config_url,
|
||||||
|
issuer: request.issuer,
|
||||||
client_id: request.client_id,
|
client_id: request.client_id,
|
||||||
client_secret,
|
client_secret,
|
||||||
scopes: request.scopes,
|
scopes: request.scopes,
|
||||||
@@ -1088,6 +1110,7 @@ fn build_provider_config_from_validate(
|
|||||||
id: provider_id.to_string(),
|
id: provider_id.to_string(),
|
||||||
enabled: request.enabled,
|
enabled: request.enabled,
|
||||||
config_url: request.config_url,
|
config_url: request.config_url,
|
||||||
|
issuer: request.issuer,
|
||||||
client_id: request.client_id,
|
client_id: request.client_id,
|
||||||
client_secret: request.client_secret.filter(|value| !value.trim().is_empty()),
|
client_secret: request.client_secret.filter(|value| !value.trim().is_empty()),
|
||||||
scopes: request.scopes,
|
scopes: request.scopes,
|
||||||
@@ -1132,6 +1155,7 @@ fn upsert_persisted_provider_config(config: &mut ServerConfig, provider_config:
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
set_kvs_value(&mut kvs, OIDC_CONFIG_URL, provider_config.config_url.clone());
|
set_kvs_value(&mut kvs, OIDC_CONFIG_URL, provider_config.config_url.clone());
|
||||||
|
set_kvs_value(&mut kvs, OIDC_ISSUER, provider_config.issuer.clone().unwrap_or_default());
|
||||||
set_kvs_value(&mut kvs, OIDC_CLIENT_ID, provider_config.client_id.clone());
|
set_kvs_value(&mut kvs, OIDC_CLIENT_ID, provider_config.client_id.clone());
|
||||||
set_kvs_value(&mut kvs, OIDC_CLIENT_SECRET, provider_config.client_secret.clone().unwrap_or_default());
|
set_kvs_value(&mut kvs, OIDC_CLIENT_SECRET, provider_config.client_secret.clone().unwrap_or_default());
|
||||||
set_kvs_value(&mut kvs, OIDC_SCOPES, provider_config.scopes.join(","));
|
set_kvs_value(&mut kvs, OIDC_SCOPES, provider_config.scopes.join(","));
|
||||||
@@ -1321,6 +1345,7 @@ mod tests {
|
|||||||
id: "default".to_string(),
|
id: "default".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: "https://idp.example.com/.well-known/openid-configuration".to_string(),
|
config_url: "https://idp.example.com/.well-known/openid-configuration".to_string(),
|
||||||
|
issuer: None,
|
||||||
client_id: "rustfs-console".to_string(),
|
client_id: "rustfs-console".to_string(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: vec!["openid".to_string()],
|
scopes: vec!["openid".to_string()],
|
||||||
@@ -1718,6 +1743,7 @@ mod tests {
|
|||||||
id: "default".to_string(),
|
id: "default".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
||||||
|
issuer: None,
|
||||||
client_id: "console".to_string(),
|
client_id: "console".to_string(),
|
||||||
client_secret: Some("secret".to_string()),
|
client_secret: Some("secret".to_string()),
|
||||||
scopes: vec!["openid".to_string(), "profile".to_string()],
|
scopes: vec!["openid".to_string(), "profile".to_string()],
|
||||||
@@ -1748,6 +1774,7 @@ mod tests {
|
|||||||
id: "kubernetes".to_string(),
|
id: "kubernetes".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
config_url: "https://example.com/.well-known/openid-configuration".to_string(),
|
||||||
|
issuer: None,
|
||||||
client_id: "test".to_string(),
|
client_id: "test".to_string(),
|
||||||
client_secret: None,
|
client_secret: None,
|
||||||
scopes: vec!["openid".to_string()],
|
scopes: vec!["openid".to_string()],
|
||||||
@@ -1785,4 +1812,39 @@ mod tests {
|
|||||||
.expect("provider KVS should exist");
|
.expect("provider KVS should exist");
|
||||||
assert_eq!(kvs.get(OIDC_HIDE_FROM_UI), EnableState::Off.to_string());
|
assert_eq!(kvs.get(OIDC_HIDE_FROM_UI), EnableState::Off.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_upsert_persists_issuer() {
|
||||||
|
let mut config = ServerConfig::new();
|
||||||
|
let provider_config = rustfs_iam::oidc::OidcProviderConfig {
|
||||||
|
id: "kubernetes".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
config_url: "http://keycloak.ns.svc.cluster.local:8080/realms/app/.well-known/openid-configuration".to_string(),
|
||||||
|
issuer: Some("https://app.local/realms/app".to_string()),
|
||||||
|
client_id: "test".to_string(),
|
||||||
|
client_secret: None,
|
||||||
|
scopes: vec!["openid".to_string()],
|
||||||
|
other_audiences: vec![],
|
||||||
|
redirect_uri: None,
|
||||||
|
redirect_uri_dynamic: true,
|
||||||
|
claim_name: "sub".to_string(),
|
||||||
|
claim_prefix: String::new(),
|
||||||
|
role_policy: String::new(),
|
||||||
|
display_name: "Kubernetes".to_string(),
|
||||||
|
groups_claim: OIDC_DEFAULT_GROUPS_CLAIM.to_string(),
|
||||||
|
roles_claim: OIDC_DEFAULT_ROLES_CLAIM.to_string(),
|
||||||
|
email_claim: OIDC_DEFAULT_EMAIL_CLAIM.to_string(),
|
||||||
|
username_claim: OIDC_DEFAULT_USERNAME_CLAIM.to_string(),
|
||||||
|
hide_from_ui: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
upsert_persisted_provider_config(&mut config, &provider_config);
|
||||||
|
|
||||||
|
let kvs = config
|
||||||
|
.0
|
||||||
|
.get(IDENTITY_OPENID_SUB_SYS)
|
||||||
|
.and_then(|m| m.get("kubernetes"))
|
||||||
|
.expect("provider KVS should exist");
|
||||||
|
assert_eq!(kvs.get(OIDC_ISSUER), "https://app.local/realms/app");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user