* [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms
@ 2021-06-25 9:20 Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 01/10] depend on proxmox-openid-rs Dietmar Maurer
` (10 more replies)
0 siblings, 11 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
This implements OpenID connect realms using the new
"proxmox-openid-rs" crate.
Note: The new src/tools/memcom.rs is from Wolfgang.
Changes since v2:
fix CachedUserInfo by using a shared memory version counter
Changes since v1:
- really fix commit message of first patch
- change api endpoints (/access/openid/{login|auth-url})
- merged all api implementation patches
Changes since preview version (for Fabian):
- fix commit message
- reserve namen 'pam' and 'pbs'
- fix 'make deb'
Dietmar Maurer (10):
depend on proxmox-openid-rs
config: new domains.cfg to configure openid realm
check_acl_path: add /access/domains and /access/openid
add API to manage openid realms
cli: add CLI to manage openid realms.
implement new helper is_active_user_id()
cleanup user/token is_active() check
api: add openid redirect/login API
ui: implement OpenId login
fix CachedUserInfo by using a shared memory version counter
Cargo.toml | 2 +
src/api2/access.rs | 4 +-
src/api2/access/domain.rs | 18 ++
src/api2/access/openid.rs | 190 ++++++++++++++++
src/api2/config/access/mod.rs | 8 +-
src/api2/config/access/openid.rs | 274 +++++++++++++++++++++++
src/bin/proxmox-backup-manager.rs | 1 +
src/bin/proxmox_backup_manager/mod.rs | 2 +
src/bin/proxmox_backup_manager/openid.rs | 99 ++++++++
src/config.rs | 1 +
src/config/acl.rs | 8 +-
src/config/cached_user_info.rs | 48 ++--
src/config/domains.rs | 173 ++++++++++++++
src/config/user.rs | 38 ++++
src/tools.rs | 3 +
src/tools/memcom.rs | 159 +++++++++++++
www/Application.js | 8 +-
www/LoginView.js | 100 ++++++++-
www/Utils.js | 8 +
19 files changed, 1115 insertions(+), 29 deletions(-)
create mode 100644 src/api2/access/openid.rs
create mode 100644 src/api2/config/access/openid.rs
create mode 100644 src/bin/proxmox_backup_manager/openid.rs
create mode 100644 src/config/domains.rs
create mode 100644 src/tools/memcom.rs
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 01/10] depend on proxmox-openid-rs
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 02/10] config: new domains.cfg to configure openid realm Dietmar Maurer
` (9 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
Cargo.toml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Cargo.toml b/Cargo.toml
index 976f18bc..adb58c7b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,6 +84,8 @@ crossbeam-channel = "0.5"
proxmox-acme-rs = "0.2.1"
+proxmox-openid = "0.6.0"
+
[features]
default = []
#valgrind = ["valgrind_request"]
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 02/10] config: new domains.cfg to configure openid realm
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 01/10] depend on proxmox-openid-rs Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 03/10] check_acl_path: add /access/domains and /access/openid Dietmar Maurer
` (8 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
Or other realmy types...
---
src/api2/access/domain.rs | 21 +++++++
src/config.rs | 1 +
src/config/domains.rs | 120 ++++++++++++++++++++++++++++++++++++++
3 files changed, 142 insertions(+)
create mode 100644 src/config/domains.rs
diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index a2fd746d..2dff9d32 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -38,10 +38,31 @@ use crate::api2::types::*;
)]
/// Authentication domain/realm index.
fn list_domains() -> Result<Value, Error> {
+
let mut list = Vec::new();
+
list.push(json!({ "realm": "pam", "comment": "Linux PAM standard authentication", "default": true }));
list.push(json!({ "realm": "pbs", "comment": "Proxmox Backup authentication server" }));
+
+ let (config, _digest) = crate::config::domains::config()?;
+
+ for (realm, (section_type, v)) in config.sections.iter() {
+ let mut item = json!({
+ "type": section_type,
+ "realm": realm,
+ });
+
+ if v["comment"].as_str().is_some() {
+ item["comment"] = v["comment"].clone();
+ }
+ list.push(item);
+
+ }
+
Ok(list.into())
+
+
+
}
pub const ROUTER: Router = Router::new()
diff --git a/src/config.rs b/src/config.rs
index b9cd6281..329315ec 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -31,6 +31,7 @@ pub mod drive;
pub mod media_pool;
pub mod tape_encryption_keys;
pub mod tape_job;
+pub mod domains;
/// Check configuration directory permissions
///
diff --git a/src/config/domains.rs b/src/config/domains.rs
new file mode 100644
index 00000000..703acfe9
--- /dev/null
+++ b/src/config/domains.rs
@@ -0,0 +1,120 @@
+use anyhow::{Error};
+use lazy_static::lazy_static;
+use std::collections::HashMap;
+use serde::{Serialize, Deserialize};
+
+use proxmox::api::{
+ api,
+ schema::*,
+ section_config::{
+ SectionConfig,
+ SectionConfigData,
+ SectionConfigPlugin,
+ }
+};
+
+use proxmox::tools::fs::{
+ open_file_locked,
+ replace_file,
+ CreateOptions,
+};
+
+use crate::api2::types::*;
+
+lazy_static! {
+ pub static ref CONFIG: SectionConfig = init();
+}
+
+
+#[api(
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "issuer-url": {
+ description: "OpenID Issuer Url",
+ type: String,
+ },
+ "client-id": {
+ description: "OpenID Client ID",
+ type: String,
+ },
+ "client-key": {
+ description: "OpenID Client Key",
+ type: String,
+ optional: true,
+ },
+ comment: {
+ optional: true,
+ schema: SINGLE_LINE_COMMENT_SCHEMA,
+ },
+ },
+)]
+#[derive(Serialize,Deserialize)]
+#[serde(rename_all="kebab-case")]
+/// OpenID configuration properties.
+pub struct OpenIdRealmConfig {
+ pub realm: String,
+ pub issuer_url: String,
+ pub client_id: String,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub client_key: Option<String>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub comment: Option<String>,
+}
+
+fn init() -> SectionConfig {
+ let obj_schema = match OpenIdRealmConfig::API_SCHEMA {
+ Schema::Object(ref obj_schema) => obj_schema,
+ _ => unreachable!(),
+ };
+
+ let plugin = SectionConfigPlugin::new("openid".to_string(), Some(String::from("realm")), obj_schema);
+ let mut config = SectionConfig::new(&REALM_ID_SCHEMA);
+ config.register_plugin(plugin);
+
+ config
+}
+
+pub const DOMAINS_CFG_FILENAME: &str = "/etc/proxmox-backup/domains.cfg";
+pub const DOMAINS_CFG_LOCKFILE: &str = "/etc/proxmox-backup/.domains.lck";
+
+/// Get exclusive lock
+pub fn lock_config() -> Result<std::fs::File, Error> {
+ open_file_locked(DOMAINS_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)
+}
+
+pub fn config() -> Result<(SectionConfigData, [u8;32]), Error> {
+
+ let content = proxmox::tools::fs::file_read_optional_string(DOMAINS_CFG_FILENAME)?
+ .unwrap_or_else(|| "".to_string());
+
+ let digest = openssl::sha::sha256(content.as_bytes());
+ let data = CONFIG.parse(DOMAINS_CFG_FILENAME, &content)?;
+ Ok((data, digest))
+}
+
+pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
+ let raw = CONFIG.write(DOMAINS_CFG_FILENAME, &config)?;
+
+ let backup_user = crate::backup::backup_user()?;
+ let mode = nix::sys::stat::Mode::from_bits_truncate(0o0640);
+ // set the correct owner/group/permissions while saving file
+ // owner(rw) = root, group(r)= backup
+ let options = CreateOptions::new()
+ .perm(mode)
+ .owner(nix::unistd::ROOT)
+ .group(backup_user.gid);
+
+ replace_file(DOMAINS_CFG_FILENAME, raw.as_bytes(), options)?;
+
+ Ok(())
+}
+
+// shell completion helper
+pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+ match config() {
+ Ok((data, _digest)) => data.sections.iter().map(|(id, _)| id.to_string()).collect(),
+ Err(_) => return vec![],
+ }
+}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 03/10] check_acl_path: add /access/domains and /access/openid
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 01/10] depend on proxmox-openid-rs Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 02/10] config: new domains.cfg to configure openid realm Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 04/10] add API to manage openid realms Dietmar Maurer
` (7 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/config/acl.rs | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/config/acl.rs b/src/config/acl.rs
index 04d42854..e468586e 100644
--- a/src/config/acl.rs
+++ b/src/config/acl.rs
@@ -283,11 +283,17 @@ pub fn check_acl_path(path: &str) -> Result<(), Error> {
return Ok(());
}
match components[1] {
- "acl" | "users" => {
+ "acl" | "users" | "domains" => {
if components_len == 2 {
return Ok(());
}
}
+ // /access/openid/{endpoint}
+ "openid" => {
+ if components_len <= 3 {
+ return Ok(());
+ }
+ }
_ => {}
}
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 04/10] add API to manage openid realms
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (2 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 03/10] check_acl_path: add /access/domains and /access/openid Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI " Dietmar Maurer
` (6 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/api2/config/access/mod.rs | 8 +-
src/api2/config/access/openid.rs | 264 +++++++++++++++++++++++++++++++
2 files changed, 271 insertions(+), 1 deletion(-)
create mode 100644 src/api2/config/access/openid.rs
diff --git a/src/api2/config/access/mod.rs b/src/api2/config/access/mod.rs
index 659815e0..6e7d98be 100644
--- a/src/api2/config/access/mod.rs
+++ b/src/api2/config/access/mod.rs
@@ -1,9 +1,15 @@
use proxmox::api::{Router, SubdirMap};
use proxmox::list_subdirs_api_method;
+use proxmox::{identity, sortable};
pub mod tfa;
+pub mod openid;
-const SUBDIRS: SubdirMap = &[("tfa", &tfa::ROUTER)];
+#[sortable]
+const SUBDIRS: SubdirMap = &sorted!([
+ ("openid", &openid::ROUTER),
+ ("tfa", &tfa::ROUTER),
+]);
pub const ROUTER: Router = Router::new()
.get(&list_subdirs_api_method!(SUBDIRS))
diff --git a/src/api2/config/access/openid.rs b/src/api2/config/access/openid.rs
new file mode 100644
index 00000000..15fddaf0
--- /dev/null
+++ b/src/api2/config/access/openid.rs
@@ -0,0 +1,264 @@
+/// Configure OpenId realms
+
+use anyhow::{bail, Error};
+use serde_json::Value;
+use ::serde::{Deserialize, Serialize};
+
+use proxmox::api::{api, Permission, Router, RpcEnvironment};
+
+use crate::config::domains::{self, OpenIdRealmConfig};
+use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_PERMISSIONS_MODIFY};
+use crate::api2::types::*;
+
+#[api(
+ input: {
+ properties: {},
+ },
+ returns: {
+ description: "List of configured OpenId realms.",
+ type: Array,
+ items: { type: OpenIdRealmConfig },
+ },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// List configured OpenId realms
+pub fn list_openid_realms(
+ _param: Value,
+ mut rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Vec<OpenIdRealmConfig>, Error> {
+
+ let (config, digest) = domains::config()?;
+
+ let list = config.convert_to_typed_array("openid")?;
+
+ rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+
+ Ok(list)
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ config: {
+ type: OpenIdRealmConfig,
+ flatten: true,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Create a new OpenId realm
+pub fn create_openid_realm(config: OpenIdRealmConfig) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, _digest) = domains::config()?;
+
+ if config.realm == "pbs" ||
+ config.realm == "pam" ||
+ domains.sections.get(&config.realm).is_some()
+ {
+ bail!("realm '{}' already exists.", config.realm);
+ }
+
+ domains.set_data(&config.realm, "openid", &config)?;
+
+ domains::save_config(&domains)?;
+
+ Ok(())
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ digest: {
+ optional: true,
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ },
+ },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Remove a OpenID realm configuration
+pub fn delete_openid_realm(
+ realm: String,
+ digest: Option<String>,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, expected_digest) = domains::config()?;
+
+ if let Some(ref digest) = digest {
+ let digest = proxmox::tools::hex_to_digest(digest)?;
+ crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+ }
+
+ if domains.sections.remove(&realm).is_none() {
+ bail!("realm '{}' does not exist.", realm);
+ }
+
+ domains::save_config(&domains)?;
+
+ Ok(())
+}
+
+#[api(
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ },
+ },
+ returns: { type: OpenIdRealmConfig },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_SYS_AUDIT, false),
+ },
+)]
+/// Read the OpenID realm configuration
+pub fn read_openid_realm(
+ realm: String,
+ mut rpcenv: &mut dyn RpcEnvironment,
+) -> Result<OpenIdRealmConfig, Error> {
+
+ let (domains, digest) = domains::config()?;
+
+ let config = domains.lookup("openid", &realm)?;
+
+ rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
+
+ Ok(config)
+}
+
+#[api()]
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all="kebab-case")]
+#[allow(non_camel_case_types)]
+/// Deletable property name
+pub enum DeletableProperty {
+ /// Delete the client key.
+ client_key,
+ /// Delete the comment property.
+ comment,
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "issuer-url": {
+ description: "OpenID Issuer Url",
+ type: String,
+ optional: true,
+ },
+ "client-id": {
+ description: "OpenID Client ID",
+ type: String,
+ optional: true,
+ },
+ "client-key": {
+ description: "OpenID Client Key",
+ type: String,
+ optional: true,
+ },
+ comment: {
+ schema: SINGLE_LINE_COMMENT_SCHEMA,
+ optional: true,
+ },
+ delete: {
+ description: "List of properties to delete.",
+ type: Array,
+ optional: true,
+ items: {
+ type: DeletableProperty,
+ }
+ },
+ digest: {
+ optional: true,
+ schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
+ },
+ },
+ },
+ returns: { type: OpenIdRealmConfig },
+ access: {
+ permission: &Permission::Privilege(&["access", "domains"], PRIV_PERMISSIONS_MODIFY, false),
+ },
+)]
+/// Update an OpenID realm configuration
+pub fn update_openid_realm(
+ realm: String,
+ issuer_url: Option<String>,
+ client_id: Option<String>,
+ client_key: Option<String>,
+ comment: Option<String>,
+ delete: Option<Vec<DeletableProperty>>,
+ digest: Option<String>,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+ let _lock = domains::lock_config()?;
+
+ let (mut domains, expected_digest) = domains::config()?;
+
+ if let Some(ref digest) = digest {
+ let digest = proxmox::tools::hex_to_digest(digest)?;
+ crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
+ }
+
+ let mut config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+
+ if let Some(delete) = delete {
+ for delete_prop in delete {
+ match delete_prop {
+ DeletableProperty::client_key => { config.client_key = None; },
+ DeletableProperty::comment => { config.comment = None; },
+ }
+ }
+ }
+
+ if let Some(comment) = comment {
+ let comment = comment.trim().to_string();
+ if comment.is_empty() {
+ config.comment = None;
+ } else {
+ config.comment = Some(comment);
+ }
+ }
+
+ if let Some(issuer_url) = issuer_url { config.issuer_url = issuer_url; }
+ if let Some(client_id) = client_id { config.client_id = client_id; }
+
+ if client_key.is_some() { config.client_key = client_key; }
+
+ domains.set_data(&realm, "openid", &config)?;
+
+ domains::save_config(&domains)?;
+
+ Ok(())
+}
+
+const ITEM_ROUTER: Router = Router::new()
+ .get(&API_METHOD_READ_OPENID_REALM)
+ .put(&API_METHOD_UPDATE_OPENID_REALM)
+ .delete(&API_METHOD_DELETE_OPENID_REALM);
+
+pub const ROUTER: Router = Router::new()
+ .get(&API_METHOD_LIST_OPENID_REALMS)
+ .post(&API_METHOD_CREATE_OPENID_REALM)
+ .match_all("id", &ITEM_ROUTER);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI to manage openid realms.
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (3 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 04/10] add API to manage openid realms Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 11:41 ` Wolfgang Bumiller
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 06/10] implement new helper is_active_user_id() Dietmar Maurer
` (5 subsequent siblings)
10 siblings, 1 reply; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/bin/proxmox-backup-manager.rs | 1 +
src/bin/proxmox_backup_manager/mod.rs | 2 +
src/bin/proxmox_backup_manager/openid.rs | 99 ++++++++++++++++++++++++
src/config/domains.rs | 9 +++
4 files changed, 111 insertions(+)
create mode 100644 src/bin/proxmox_backup_manager/openid.rs
diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs
index c3806a31..461d45bd 100644
--- a/src/bin/proxmox-backup-manager.rs
+++ b/src/bin/proxmox-backup-manager.rs
@@ -354,6 +354,7 @@ fn main() {
.insert("network", network_commands())
.insert("node", node_commands())
.insert("user", user_commands())
+ .insert("openid", openid_commands())
.insert("remote", remote_commands())
.insert("garbage-collection", garbage_collection_commands())
.insert("acme", acme_mgmt_cli())
diff --git a/src/bin/proxmox_backup_manager/mod.rs b/src/bin/proxmox_backup_manager/mod.rs
index 21004bbe..a3a16246 100644
--- a/src/bin/proxmox_backup_manager/mod.rs
+++ b/src/bin/proxmox_backup_manager/mod.rs
@@ -24,3 +24,5 @@ mod disk;
pub use disk::*;
mod node;
pub use node::*;
+mod openid;
+pub use openid::*;
diff --git a/src/bin/proxmox_backup_manager/openid.rs b/src/bin/proxmox_backup_manager/openid.rs
new file mode 100644
index 00000000..13915339
--- /dev/null
+++ b/src/bin/proxmox_backup_manager/openid.rs
@@ -0,0 +1,99 @@
+use anyhow::Error;
+use serde_json::Value;
+
+use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler};
+
+use proxmox_backup::{config, api2, api2::types::REALM_ID_SCHEMA};
+
+
+#[api(
+ input: {
+ properties: {
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+/// List configured OpenId realms
+fn list_openid_realms(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+ let output_format = get_output_format(¶m);
+
+ let info = &api2::config::access::openid::API_METHOD_LIST_OPENID_REALMS;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options()
+ .column(ColumnConfig::new("realm"))
+ .column(ColumnConfig::new("issuer-url"))
+ .column(ColumnConfig::new("comment"));
+
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(Value::Null)
+}
+#[api(
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "output-format": {
+ schema: OUTPUT_FORMAT,
+ optional: true,
+ },
+ }
+ }
+)]
+
+/// Show OpenID realm configuration
+fn show_openid_realm(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
+
+ let output_format = get_output_format(¶m);
+
+ let info = &api2::config::access::openid::API_METHOD_READ_OPENID_REALM;
+ let mut data = match info.handler {
+ ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+ _ => unreachable!(),
+ };
+
+ let options = default_table_format_options();
+ format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
+ Ok(Value::Null)
+}
+
+pub fn openid_commands() -> CommandLineInterface {
+
+ let cmd_def = CliCommandMap::new()
+ .insert("list", CliCommand::new(&&API_METHOD_LIST_OPENID_REALMS))
+ .insert("show", CliCommand::new(&&API_METHOD_SHOW_OPENID_REALM)
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("create",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_CREATE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("update",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_UPDATE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ .insert("delete",
+ CliCommand::new(&api2::config::access::openid::API_METHOD_DELETE_OPENID_REALM)
+ .arg_param(&["realm"])
+ .arg_param(&["realm"])
+ .completion_cb("realm", config::domains::complete_openid_realm_name)
+ )
+ ;
+
+ cmd_def.into()
+}
diff --git a/src/config/domains.rs b/src/config/domains.rs
index 703acfe9..3cdd4174 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -118,3 +118,12 @@ pub fn complete_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<
Err(_) => return vec![],
}
}
+
+pub fn complete_openid_realm_name(_arg: &str, _param: &HashMap<String, String>) -> Vec<String> {
+ match config() {
+ Ok((data, _digest)) => data.sections.iter()
+ .filter_map(|(id, (t, _))| if t == "openid" { Some(id.to_string()) } else { None })
+ .collect(),
+ Err(_) => return vec![],
+ }
+}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 06/10] implement new helper is_active_user_id()
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (4 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI " Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 07/10] cleanup user/token is_active() check Dietmar Maurer
` (4 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/config/cached_user_info.rs | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs
index c85d643c..a574043f 100644
--- a/src/config/cached_user_info.rs
+++ b/src/config/cached_user_info.rs
@@ -65,10 +65,8 @@ impl CachedUserInfo {
}
}
- /// Test if a authentication id is enabled and not expired
- pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
- let userid = auth_id.user();
-
+ /// Test if a user_id is enabled and not expired
+ pub fn is_active_user_id(&self, userid: &Userid) -> bool {
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
if !info.enable.unwrap_or(true) {
return false;
@@ -78,7 +76,17 @@ impl CachedUserInfo {
return false;
}
}
+ true
} else {
+ false
+ }
+ }
+
+ /// Test if a authentication id is enabled and not expired
+ pub fn is_active_auth_id(&self, auth_id: &Authid) -> bool {
+ let userid = auth_id.user();
+
+ if !self.is_active_user_id(userid) {
return false;
}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 07/10] cleanup user/token is_active() check
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (5 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 06/10] implement new helper is_active_user_id() Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 08/10] api: add openid redirect/login API Dietmar Maurer
` (3 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/config/cached_user_info.rs | 25 ++++---------------------
src/config/user.rs | 32 ++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs
index a574043f..6cb64162 100644
--- a/src/config/cached_user_info.rs
+++ b/src/config/cached_user_info.rs
@@ -7,6 +7,7 @@ use anyhow::{Error, bail};
use proxmox::api::section_config::SectionConfigData;
use lazy_static::lazy_static;
use proxmox::api::UserInformation;
+use proxmox::tools::time::epoch_i64;
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
use super::user::{ApiToken, User};
@@ -18,8 +19,6 @@ pub struct CachedUserInfo {
acl_tree: Arc<AclTree>,
}
-fn now() -> i64 { unsafe { libc::time(std::ptr::null_mut()) } }
-
struct ConfigCache {
data: Option<Arc<CachedUserInfo>>,
last_update: i64,
@@ -35,7 +34,7 @@ impl CachedUserInfo {
/// Returns a cached instance (up to 5 seconds old).
pub fn new() -> Result<Arc<Self>, Error> {
- let now = now();
+ let now = epoch_i64();
{ // limit scope
let cache = CACHED_CONFIG.read().unwrap();
if (now - cache.last_update) < 5 {
@@ -68,15 +67,7 @@ impl CachedUserInfo {
/// Test if a user_id is enabled and not expired
pub fn is_active_user_id(&self, userid: &Userid) -> bool {
if let Ok(info) = self.user_cfg.lookup::<User>("user", userid.as_str()) {
- if !info.enable.unwrap_or(true) {
- return false;
- }
- if let Some(expire) = info.expire {
- if expire > 0 && expire <= now() {
- return false;
- }
- }
- true
+ info.is_active()
} else {
false
}
@@ -92,15 +83,7 @@ impl CachedUserInfo {
if auth_id.is_token() {
if let Ok(info) = self.user_cfg.lookup::<ApiToken>("token", &auth_id.to_string()) {
- if !info.enable.unwrap_or(true) {
- return false;
- }
- if let Some(expire) = info.expire {
- if expire > 0 && expire <= now() {
- return false;
- }
- }
- return true;
+ return info.is_active();
} else {
return false;
}
diff --git a/src/config/user.rs b/src/config/user.rs
index ff7e54e4..28e81876 100644
--- a/src/config/user.rs
+++ b/src/config/user.rs
@@ -83,6 +83,22 @@ pub struct ApiToken {
pub expire: Option<i64>,
}
+impl ApiToken {
+
+ pub fn is_active(&self) -> bool {
+ if !self.enable.unwrap_or(true) {
+ return false;
+ }
+ if let Some(expire) = self.expire {
+ let now = proxmox::tools::time::epoch_i64();
+ if expire > 0 && expire <= now {
+ return false;
+ }
+ }
+ true
+ }
+}
+
#[api(
properties: {
userid: {
@@ -132,6 +148,22 @@ pub struct User {
pub email: Option<String>,
}
+impl User {
+
+ pub fn is_active(&self) -> bool {
+ if !self.enable.unwrap_or(true) {
+ return false;
+ }
+ if let Some(expire) = self.expire {
+ let now = proxmox::tools::time::epoch_i64();
+ if expire > 0 && expire <= now {
+ return false;
+ }
+ }
+ true
+ }
+}
+
fn init() -> SectionConfig {
let mut config = SectionConfig::new(&Authid::API_SCHEMA);
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 08/10] api: add openid redirect/login API
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (6 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 07/10] cleanup user/token is_active() check Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 09/10] ui: implement OpenId login Dietmar Maurer
` (2 subsequent siblings)
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access.rs | 4 +-
src/api2/access/domain.rs | 3 -
src/api2/access/openid.rs | 192 +++++++++++++++++++++++++++++++
src/api2/config/access/openid.rs | 10 ++
src/config/domains.rs | 44 +++++++
5 files changed, 249 insertions(+), 4 deletions(-)
create mode 100644 src/api2/access/openid.rs
diff --git a/src/api2/access.rs b/src/api2/access.rs
index 46725c97..e5430f62 100644
--- a/src/api2/access.rs
+++ b/src/api2/access.rs
@@ -26,6 +26,7 @@ pub mod domain;
pub mod role;
pub mod tfa;
pub mod user;
+pub mod openid;
#[allow(clippy::large_enum_variant)]
enum AuthResult {
@@ -335,7 +336,7 @@ pub fn list_permissions(
let auth_id = match auth_id {
Some(auth_id) if auth_id == current_auth_id => current_auth_id,
Some(auth_id) => {
- if user_privs & PRIV_SYS_AUDIT != 0
+ if user_privs & PRIV_SYS_AUDIT != 0
|| (auth_id.is_token()
&& !current_auth_id.is_token()
&& auth_id.user() == current_auth_id.user())
@@ -423,6 +424,7 @@ const SUBDIRS: SubdirMap = &sorted!([
&Router::new().get(&API_METHOD_LIST_PERMISSIONS)
),
("ticket", &Router::new().post(&API_METHOD_CREATE_TICKET)),
+ ("openid", &openid::ROUTER),
("domains", &domain::ROUTER),
("roles", &role::ROUTER),
("users", &user::ROUTER),
diff --git a/src/api2/access/domain.rs b/src/api2/access/domain.rs
index 2dff9d32..69809acc 100644
--- a/src/api2/access/domain.rs
+++ b/src/api2/access/domain.rs
@@ -60,9 +60,6 @@ fn list_domains() -> Result<Value, Error> {
}
Ok(list.into())
-
-
-
}
pub const ROUTER: Router = Router::new()
diff --git a/src/api2/access/openid.rs b/src/api2/access/openid.rs
new file mode 100644
index 00000000..52ec4311
--- /dev/null
+++ b/src/api2/access/openid.rs
@@ -0,0 +1,192 @@
+//! OpenID redirect/login API
+use std::convert::TryFrom;
+
+use anyhow::{bail, Error};
+
+use serde_json::{json, Value};
+
+use proxmox::api::router::{Router, SubdirMap};
+use proxmox::api::{api, Permission, RpcEnvironment};
+use proxmox::{list_subdirs_api_method};
+use proxmox::{identity, sortable};
+use proxmox::tools::fs::open_file_locked;
+
+use proxmox_openid::OpenIdAuthenticator;
+
+use crate::server::ticket::ApiTicket;
+use crate::tools::ticket::Ticket;
+
+use crate::config::domains::{OpenIdUserAttribute, OpenIdRealmConfig};
+use crate::config::cached_user_info::CachedUserInfo;
+
+use crate::api2::types::*;
+use crate::auth_helpers::*;
+
+#[api(
+ input: {
+ properties: {
+ state: {
+ description: "OpenId state.",
+ type: String,
+ },
+ code: {
+ description: "OpenId authorization code.",
+ type: String,
+ },
+ "redirect-url": {
+ description: "Redirection Url. The client should set this to used server url.",
+ type: String,
+ },
+ },
+ },
+ returns: {
+ properties: {
+ username: {
+ type: String,
+ description: "User name.",
+ },
+ ticket: {
+ type: String,
+ description: "Auth ticket.",
+ },
+ CSRFPreventionToken: {
+ type: String,
+ description: "Cross Site Request Forgery Prevention Token.",
+ },
+ },
+ },
+ protected: true,
+ access: {
+ permission: &Permission::World,
+ },
+)]
+/// Verify OpenID authorization code and create a ticket
+///
+/// Returns: An authentication ticket with additional infos.
+pub fn openid_login(
+ state: String,
+ code: String,
+ redirect_url: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<Value, Error> {
+ let user_info = CachedUserInfo::new()?;
+
+ let (realm, private_auth_state) =
+ OpenIdAuthenticator::verify_public_auth_state(PROXMOX_BACKUP_RUN_DIR_M!(), &state)?;
+
+ let (domains, _digest) = crate::config::domains::config()?;
+ let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+
+ let open_id = config.authenticator(&redirect_url)?;
+
+ let info = open_id.verify_authorization_code(&code, &private_auth_state)?;
+
+ // eprintln!("VERIFIED {} {:?} {:?}", info.subject().as_str(), info.name(), info.email());
+
+ let unique_name = match config.user_attr {
+ None | Some(OpenIdUserAttribute::Subject) => info.subject().as_str(),
+ Some(OpenIdUserAttribute::Username) => {
+ match info.preferred_username() {
+ Some(name) => name.as_str(),
+ None => bail!("missing claim 'preferred_name'"),
+ }
+ }
+ Some(OpenIdUserAttribute::Email) => {
+ match info.email() {
+ Some(name) => name.as_str(),
+ None => bail!("missing claim 'email'"),
+ }
+ }
+ };
+
+ let user_id = Userid::try_from(format!("{}@{}", unique_name, realm))?;
+
+ if !user_info.is_active_user_id(&user_id) {
+ if config.autocreate.unwrap_or(false) {
+ use crate::config::user;
+ let _lock = open_file_locked(user::USER_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
+ let user = user::User {
+ userid: user_id.clone(),
+ comment: None,
+ enable: None,
+ expire: None,
+ firstname: info.given_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
+ lastname: info.family_name().and_then(|n| n.get(None)).map(|n| n.to_string()),
+ email: info.email().map(|e| e.to_string()),
+ };
+ let (mut config, _digest) = user::config()?;
+ if config.sections.get(user.userid.as_str()).is_some() {
+ bail!("autocreate user failed - '{}' already exists.", user.userid);
+ }
+ config.set_data(user.userid.as_str(), "user", &user)?;
+ user::save_config(&config)?;
+ // fixme: replace sleep with shared memory change notification
+ std::thread::sleep(std::time::Duration::new(6, 0));
+ } else {
+ bail!("user account '{}' missing, disabled or expired.", user_id);
+ }
+ }
+
+ let api_ticket = ApiTicket::full(user_id.clone());
+ let ticket = Ticket::new("PBS", &api_ticket)?.sign(private_auth_key(), None)?;
+ let token = assemble_csrf_prevention_token(csrf_secret(), &user_id);
+
+ crate::server::rest::auth_logger()?
+ .log(format!("successful auth for user '{}'", user_id));
+
+ Ok(json!({
+ "username": user_id,
+ "ticket": ticket,
+ "CSRFPreventionToken": token,
+ }))
+}
+
+#[api(
+ protected: true,
+ input: {
+ properties: {
+ realm: {
+ schema: REALM_ID_SCHEMA,
+ },
+ "redirect-url": {
+ description: "Redirection Url. The client should set this to used server url.",
+ type: String,
+ },
+ },
+ },
+ returns: {
+ description: "Redirection URL.",
+ type: String,
+ },
+ access: {
+ description: "Anyone can access this (before the user is authenticated).",
+ permission: &Permission::World,
+ },
+)]
+/// Create OpenID Redirect Session
+fn openid_auth_url(
+ realm: String,
+ redirect_url: String,
+ _rpcenv: &mut dyn RpcEnvironment,
+) -> Result<String, Error> {
+
+ let (domains, _digest) = crate::config::domains::config()?;
+ let config: OpenIdRealmConfig = domains.lookup("openid", &realm)?;
+
+ let open_id = config.authenticator(&redirect_url)?;
+
+ let url = open_id.authorize_url(PROXMOX_BACKUP_RUN_DIR_M!(), &realm)?
+ .to_string();
+
+ Ok(url.into())
+}
+
+#[sortable]
+const SUBDIRS: SubdirMap = &sorted!([
+ ("login", &Router::new().post(&API_METHOD_OPENID_LOGIN)),
+ ("auth-url", &Router::new().post(&API_METHOD_OPENID_AUTH_URL)),
+]);
+
+pub const ROUTER: Router = Router::new()
+ .get(&list_subdirs_api_method!(SUBDIRS))
+ .subdirs(SUBDIRS);
diff --git a/src/api2/config/access/openid.rs b/src/api2/config/access/openid.rs
index 15fddaf0..9325de94 100644
--- a/src/api2/config/access/openid.rs
+++ b/src/api2/config/access/openid.rs
@@ -153,6 +153,8 @@ pub enum DeletableProperty {
client_key,
/// Delete the comment property.
comment,
+ /// Delete the autocreate property
+ autocreate,
}
#[api(
@@ -177,6 +179,11 @@ pub enum DeletableProperty {
type: String,
optional: true,
},
+ autocreate: {
+ description: "Automatically create users if they do not exist.",
+ optional: true,
+ type: bool,
+ },
comment: {
schema: SINGLE_LINE_COMMENT_SCHEMA,
optional: true,
@@ -206,6 +213,7 @@ pub fn update_openid_realm(
issuer_url: Option<String>,
client_id: Option<String>,
client_key: Option<String>,
+ autocreate: Option<bool>,
comment: Option<String>,
delete: Option<Vec<DeletableProperty>>,
digest: Option<String>,
@@ -228,6 +236,7 @@ pub fn update_openid_realm(
match delete_prop {
DeletableProperty::client_key => { config.client_key = None; },
DeletableProperty::comment => { config.comment = None; },
+ DeletableProperty::autocreate => { config.autocreate = None; },
}
}
}
@@ -245,6 +254,7 @@ pub fn update_openid_realm(
if let Some(client_id) = client_id { config.client_id = client_id; }
if client_key.is_some() { config.client_key = client_key; }
+ if autocreate.is_some() { config.autocreate = autocreate; }
domains.set_data(&realm, "openid", &config)?;
diff --git a/src/config/domains.rs b/src/config/domains.rs
index 3cdd4174..c456bc45 100644
--- a/src/config/domains.rs
+++ b/src/config/domains.rs
@@ -3,6 +3,8 @@ use lazy_static::lazy_static;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
+use proxmox_openid::{OpenIdAuthenticator, OpenIdConfig};
+
use proxmox::api::{
api,
schema::*,
@@ -25,6 +27,22 @@ lazy_static! {
pub static ref CONFIG: SectionConfig = init();
}
+#[api()]
+#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+/// Use the value of this attribute/claim as unique user name. It is
+/// up to the identity provider to guarantee the uniqueness. The
+/// OpenID specification only guarantees that Subject ('sub') is unique. Also
+/// make sure that the user is not allowed to change that attribute by
+/// himself!
+pub enum OpenIdUserAttribute {
+ /// Subject (OpenId 'sub' claim)
+ Subject,
+ /// Username (OpenId 'preferred_username' claim)
+ Username,
+ /// Email (OpenId 'email' claim)
+ Email,
+}
#[api(
properties: {
@@ -48,6 +66,16 @@ lazy_static! {
optional: true,
schema: SINGLE_LINE_COMMENT_SCHEMA,
},
+ autocreate: {
+ description: "Automatically create users if they do not exist.",
+ optional: true,
+ type: bool,
+ default: false,
+ },
+ "user-attr": {
+ type: OpenIdUserAttribute,
+ optional: true,
+ },
},
)]
#[derive(Serialize,Deserialize)]
@@ -61,6 +89,22 @@ pub struct OpenIdRealmConfig {
pub client_key: Option<String>,
#[serde(skip_serializing_if="Option::is_none")]
pub comment: Option<String>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub autocreate: Option<bool>,
+ #[serde(skip_serializing_if="Option::is_none")]
+ pub user_attr: Option<OpenIdUserAttribute>,
+}
+
+impl OpenIdRealmConfig {
+
+ pub fn authenticator(&self, redirect_url: &str) -> Result<OpenIdAuthenticator, Error> {
+ let config = OpenIdConfig {
+ issuer_url: self.issuer_url.clone(),
+ client_id: self.client_id.clone(),
+ client_key: self.client_key.clone(),
+ };
+ OpenIdAuthenticator::discover(&config, redirect_url)
+ }
}
fn init() -> SectionConfig {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 09/10] ui: implement OpenId login
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (7 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 08/10] api: add openid redirect/login API Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 10/10] fix CachedUserInfo by using a shared memory version counter Dietmar Maurer
2021-06-29 9:50 ` [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Fabian Grünbichler
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
www/Application.js | 8 +++-
www/LoginView.js | 100 ++++++++++++++++++++++++++++++++++++++++++++-
www/Utils.js | 8 ++++
3 files changed, 114 insertions(+), 2 deletions(-)
diff --git a/www/Application.js b/www/Application.js
index c4df600e..20ae26bd 100644
--- a/www/Application.js
+++ b/www/Application.js
@@ -49,9 +49,15 @@ Ext.define('PBS.Application', {
var provider = new Ext.state.LocalStorageProvider({ prefix: 'ext-pbs-' });
Ext.state.Manager.setProvider(provider);
+ let openid_login = false;
+ let param = PBS.Utils.openid_login_param();
+ if (param !== undefined) {
+ openid_login = true;
+ }
+
// show login window if not loggedin
var loggedin = Proxmox.Utils.authOK();
- if (!loggedin) {
+ if (openid_login || !loggedin) {
me.changeView('loginview', true);
} else {
me.changeView('mainview', true);
diff --git a/www/LoginView.js b/www/LoginView.js
index 6dd09646..ff3b5540 100644
--- a/www/LoginView.js
+++ b/www/LoginView.js
@@ -2,6 +2,21 @@ Ext.define('PBS.LoginView', {
extend: 'Ext.container.Container',
xtype: 'loginview',
+ viewModel: {
+ data: {
+ openid: false,
+ },
+ formulas: {
+ button_text: function(get) {
+ if (get("openid") === true) {
+ return gettext("Login (OpenID redirect)");
+ } else {
+ return gettext("Login");
+ }
+ },
+ },
+ },
+
controller: {
xclass: 'Ext.app.ViewController',
@@ -15,8 +30,33 @@ Ext.define('PBS.LoginView', {
return;
}
+ let redirect_url = location.origin;
+
let params = loginForm.getValues();
+ if (this.getViewModel().data.openid === true) {
+ let realm = params.realm;
+ try {
+ let resp = await PBS.Async.api2({
+ url: '/api2/extjs/access/openid/auth-url',
+ params: {
+ realm: realm,
+ "redirect-url": redirect_url,
+ },
+ method: 'POST',
+ });
+ window.location = resp.result.data;
+ } catch (error) {
+ Proxmox.Utils.authClear();
+ loginForm.unmask();
+ Ext.MessageBox.alert(
+ gettext('Error'),
+ gettext('OpenId redirect failed. Please try again<br>Error: ' + error),
+ );
+ }
+ return;
+ }
+
params.username = params.username + '@' + params.realm;
delete params.realm;
@@ -98,6 +138,14 @@ Ext.define('PBS.LoginView', {
window.location.reload();
},
},
+ 'field[name=realm]': {
+ change: function(f, value) {
+ let record = f.store.getById(value);
+ if (record === undefined) return;
+ let data = record.data;
+ this.getViewModel().set("openid", data.type === "openid");
+ },
+ },
'button[reference=loginButton]': {
click: 'submitForm',
},
@@ -116,6 +164,43 @@ Ext.define('PBS.LoginView', {
var pwField = this.lookupReference('passwordField');
pwField.focus();
}
+
+ let param = PBS.Utils.openid_login_param();
+ if (param !== undefined) {
+ Proxmox.Utils.authClear();
+
+ let loginForm = this.lookupReference('loginForm');
+ loginForm.mask(gettext('OpenID login - please wait...'), 'x-mask-loading');
+
+ let redirect_url = location.origin;
+
+ Proxmox.Utils.API2Request({
+ url: '/api2/extjs/access/openid/login',
+ params: {
+ state: param.state,
+ code: param.code,
+ "redirect-url": redirect_url,
+ },
+ method: 'POST',
+ failure: function(response) {
+ loginForm.unmask();
+ Ext.MessageBox.alert(
+ gettext('Error'),
+ gettext('Login failed. Please try again<br>Error: ' + response.htmlStatus),
+ function() {
+ window.location = redirect_url;
+ },
+ );
+ },
+ success: function(response, options) {
+ loginForm.unmask();
+ let data = response.result.data;
+ PBS.Utils.updateLoginData(data);
+ PBS.app.changeView('mainview');
+ history.replaceState(null, '', redirect_url + '#pbsDashboard');
+ },
+ });
+ }
},
},
},
@@ -191,6 +276,10 @@ Ext.define('PBS.LoginView', {
itemId: 'usernameField',
reference: 'usernameField',
stateId: 'login-username',
+ bind: {
+ visible: "{!openid}",
+ disabled: "{openid}",
+ },
},
{
xtype: 'textfield',
@@ -199,6 +288,10 @@ Ext.define('PBS.LoginView', {
name: 'password',
itemId: 'passwordField',
reference: 'passwordField',
+ bind: {
+ visible: "{!openid}",
+ disabled: "{openid}",
+ },
},
{
xtype: 'pmxRealmComboBox',
@@ -223,9 +316,14 @@ Ext.define('PBS.LoginView', {
labelWidth: 250,
labelAlign: 'right',
submitValue: false,
+ bind: {
+ visible: "{!openid}",
+ },
},
{
- text: gettext('Login'),
+ bind: {
+ text: "{button_text}",
+ },
reference: 'loginButton',
formBind: true,
},
diff --git a/www/Utils.js b/www/Utils.js
index 6b378355..677f2204 100644
--- a/www/Utils.js
+++ b/www/Utils.js
@@ -326,6 +326,14 @@ Ext.define('PBS.Utils', {
};
},
+ openid_login_param: function() {
+ let param = Ext.Object.fromQueryString(window.location.search);
+ if (param.state !== undefined && param.code !== undefined) {
+ return param;
+ }
+ return undefined;
+ },
+
calculate_dedup_factor: function(gcstatus) {
let dedup = 1.0;
if (gcstatus['disk-bytes'] > 0) {
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [pbs-devel] [PATCH proxmox-backup v3 10/10] fix CachedUserInfo by using a shared memory version counter
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (8 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 09/10] ui: implement OpenId login Dietmar Maurer
@ 2021-06-25 9:20 ` Dietmar Maurer
2021-06-29 9:50 ` [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Fabian Grünbichler
10 siblings, 0 replies; 13+ messages in thread
From: Dietmar Maurer @ 2021-06-25 9:20 UTC (permalink / raw)
To: pbs-devel
---
src/api2/access/openid.rs | 2 -
src/config/cached_user_info.rs | 13 ++-
src/config/user.rs | 6 ++
src/tools.rs | 3 +
src/tools/memcom.rs | 159 +++++++++++++++++++++++++++++++++
5 files changed, 179 insertions(+), 4 deletions(-)
create mode 100644 src/tools/memcom.rs
diff --git a/src/api2/access/openid.rs b/src/api2/access/openid.rs
index 52ec4311..27e3c08c 100644
--- a/src/api2/access/openid.rs
+++ b/src/api2/access/openid.rs
@@ -120,8 +120,6 @@ pub fn openid_login(
}
config.set_data(user.userid.as_str(), "user", &user)?;
user::save_config(&config)?;
- // fixme: replace sleep with shared memory change notification
- std::thread::sleep(std::time::Duration::new(6, 0));
} else {
bail!("user account '{}' missing, disabled or expired.", user_id);
}
diff --git a/src/config/cached_user_info.rs b/src/config/cached_user_info.rs
index 6cb64162..a4c4604f 100644
--- a/src/config/cached_user_info.rs
+++ b/src/config/cached_user_info.rs
@@ -12,6 +12,7 @@ use proxmox::tools::time::epoch_i64;
use super::acl::{AclTree, ROLE_NAMES, ROLE_ADMIN};
use super::user::{ApiToken, User};
use crate::api2::types::{Authid, Userid};
+use crate::tools::Memcom;
/// Cache User/Group/Token/Acl configuration data for fast permission tests
pub struct CachedUserInfo {
@@ -22,11 +23,12 @@ pub struct CachedUserInfo {
struct ConfigCache {
data: Option<Arc<CachedUserInfo>>,
last_update: i64,
+ last_user_cache_generation: usize,
}
lazy_static! {
static ref CACHED_CONFIG: RwLock<ConfigCache> = RwLock::new(
- ConfigCache { data: None, last_update: 0 }
+ ConfigCache { data: None, last_update: 0, last_user_cache_generation: 0 }
);
}
@@ -35,9 +37,15 @@ impl CachedUserInfo {
/// Returns a cached instance (up to 5 seconds old).
pub fn new() -> Result<Arc<Self>, Error> {
let now = epoch_i64();
+
+ let memcom = Memcom::new()?;
+ let user_cache_generation = memcom.user_cache_generation();
+
{ // limit scope
let cache = CACHED_CONFIG.read().unwrap();
- if (now - cache.last_update) < 5 {
+ if (user_cache_generation == cache.last_user_cache_generation) &&
+ ((now - cache.last_update) < 5)
+ {
if let Some(ref config) = cache.data {
return Ok(config.clone());
}
@@ -51,6 +59,7 @@ impl CachedUserInfo {
let mut cache = CACHED_CONFIG.write().unwrap();
cache.last_update = now;
+ cache.last_user_cache_generation = user_cache_generation;
cache.data = Some(config.clone());
Ok(config)
diff --git a/src/config/user.rs b/src/config/user.rs
index 28e81876..bdec5fc1 100644
--- a/src/config/user.rs
+++ b/src/config/user.rs
@@ -18,6 +18,7 @@ use proxmox::api::{
use proxmox::tools::{fs::replace_file, fs::CreateOptions};
use crate::api2::types::*;
+use crate::tools::Memcom;
lazy_static! {
pub static ref CONFIG: SectionConfig = init();
@@ -270,6 +271,11 @@ pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
replace_file(USER_CFG_FILENAME, raw.as_bytes(), options)?;
+ // increase user cache generation
+ // We use this in CachedUserInfo
+ let memcom = Memcom::new()?;
+ memcom.increase_user_cache_generation();
+
Ok(())
}
diff --git a/src/tools.rs b/src/tools.rs
index 59599339..3b0bf087 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -38,6 +38,9 @@ pub mod format;
pub mod fs;
pub mod fuse_loop;
+mod memcom;
+pub use memcom::Memcom;
+
pub mod json;
pub mod logrotate;
pub mod loopdev;
diff --git a/src/tools/memcom.rs b/src/tools/memcom.rs
new file mode 100644
index 00000000..139a789a
--- /dev/null
+++ b/src/tools/memcom.rs
@@ -0,0 +1,159 @@
+//! Memory based communication channel between proxy & daemon for things such as cache
+//! invalidation.
+
+use std::ffi::CString;
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use anyhow::{bail, format_err, Error};
+use nix::errno::Errno;
+use nix::fcntl::OFlag;
+use nix::sys::mman::{MapFlags, ProtFlags};
+use nix::sys::stat::Mode;
+use once_cell::sync::OnceCell;
+
+use proxmox::sys::error::SysError;
+use proxmox::tools::fd::Fd;
+use proxmox::tools::mmap::Mmap;
+
+/// In-memory communication channel.
+pub struct Memcom {
+ mmap: Mmap<u8>,
+}
+
+#[repr(C)]
+struct Head {
+ // User (user.cfg) cache generation/version.
+ user_cache_generation: AtomicUsize,
+}
+
+static INSTANCE: OnceCell<Arc<Memcom>> = OnceCell::new();
+
+const MEMCOM_FILE_PATH: &str = rundir!("/proxmox-backup-memcom");
+
+impl Memcom {
+
+ /// Open the memory based communication channel singleton.
+ pub fn new() -> Result<Arc<Self>, Error> {
+ INSTANCE.get_or_try_init(Self::open).map(Arc::clone)
+ }
+
+ // Actual work of `new`:
+ fn open() -> Result<Arc<Self>, Error> {
+ let fd = match open_existing() {
+ Ok(fd) => fd,
+ Err(err) if err.not_found() => create_new()?,
+ Err(err) => bail!("failed to open {} - {}", MEMCOM_FILE_PATH, err),
+ };
+
+ let mmap = unsafe {
+ Mmap::<u8>::map_fd(
+ fd.as_raw_fd(),
+ 0,
+ 4096,
+ ProtFlags::PROT_READ | ProtFlags::PROT_WRITE,
+ MapFlags::MAP_SHARED | MapFlags::MAP_NORESERVE | MapFlags::MAP_POPULATE,
+ )?
+ };
+
+ Ok(Arc::new(Self { mmap }))
+ }
+
+ // Shortcut to get the mapped `Head` as a `Head`.
+ fn head(&self) -> &Head {
+ unsafe { &*(self.mmap.as_ptr() as *const u8 as *const Head) }
+ }
+
+ /// Returns the user cache generation number.
+ pub fn user_cache_generation(&self) -> usize {
+ self.head().user_cache_generation.load(Ordering::Acquire)
+ }
+
+ /// Increase the user cache generation number.
+ pub fn increase_user_cache_generation(&self) {
+ self.head()
+ .user_cache_generation
+ .fetch_add(1, Ordering::AcqRel);
+ }
+}
+
+/// The fast path opens an existing file.
+fn open_existing() -> Result<Fd, nix::Error> {
+ Fd::open(MEMCOM_FILE_PATH, OFlag::O_RDWR | OFlag::O_CLOEXEC, Mode::empty())
+}
+
+/// Since we need to initialize the file, we also need a solid slow path where we create the file.
+/// In order to make sure the next user's `open()` vs `mmap()` race against our `truncate()` call,
+/// we create it in a temporary location and rotate it in place.
+fn create_new() -> Result<Fd, Error> {
+ // create a temporary file:
+ let temp_file_name = format!("{}.{}", MEMCOM_FILE_PATH, unsafe { libc::getpid() });
+ let fd = Fd::open(
+ temp_file_name.as_str(),
+ OFlag::O_CREAT | OFlag::O_EXCL | OFlag::O_RDWR | OFlag::O_CLOEXEC,
+ Mode::from_bits_truncate(0o660),
+ ).map_err(|err| {
+ format_err!(
+ "failed to create new in-memory communication file at {} - {}",
+ temp_file_name,
+ err
+ )
+ })?;
+
+ // let it be a page in size, it'll be initialized to zero by the kernel
+ nix::unistd::ftruncate(fd.as_raw_fd(), 4096)
+ .map_err(|err| format_err!("failed to set size of {} - {}", temp_file_name, err))?;
+
+ // if this is the pbs-daemon (running as root) rather than the proxy (running as backup user),
+ // make sure the backup user can access the file:
+ if let Ok(backup_user) = crate::backup::backup_user() {
+ match nix::unistd::fchown(fd.as_raw_fd(), None, Some(backup_user.gid)) {
+ Ok(()) => (),
+ Err(err) if err.is_errno(Errno::EPERM) => {
+ // we're not the daemon (root), so the file is already owned by the backup user
+ }
+ Err(err) => bail!(
+ "failed to set group to 'backup' for {} - {}",
+ temp_file_name,
+ err
+ ),
+ }
+ }
+
+ // rotate the file into place, but use `RENAME_NOREPLACE`, so in case 2 processes race against
+ // the initialization, the first one wins!
+ // TODO: nicer `renameat2()` wrapper in `proxmox::sys`?
+ let c_file_name = CString::new(temp_file_name.as_bytes()).unwrap();
+ let new_path = CString::new(MEMCOM_FILE_PATH).unwrap();
+ let rc = unsafe {
+ libc::renameat2(
+ -1,
+ c_file_name.as_ptr(),
+ -1,
+ new_path.as_ptr(),
+ libc::RENAME_NOREPLACE,
+ )
+ };
+ if rc == 0 {
+ return Ok(fd);
+ }
+ let err = io::Error::last_os_error();
+
+ // if another process has already raced ahead and created the file, let's just open theirs
+ // instead:
+ if err.kind() == io::ErrorKind::AlreadyExists {
+ // someone beat us to it:
+ drop(fd);
+ return open_existing().map_err(Error::from);
+ }
+
+ // for any other errors, just bail out
+ bail!(
+ "failed to move file at {} into place at {} - {}",
+ temp_file_name,
+ MEMCOM_FILE_PATH,
+ err
+ );
+}
--
2.30.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI to manage openid realms.
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI " Dietmar Maurer
@ 2021-06-25 11:41 ` Wolfgang Bumiller
0 siblings, 0 replies; 13+ messages in thread
From: Wolfgang Bumiller @ 2021-06-25 11:41 UTC (permalink / raw)
To: Dietmar Maurer; +Cc: pbs-devel
just a minor cleanup:
On Fri, Jun 25, 2021 at 11:20:45AM +0200, Dietmar Maurer wrote:
> diff --git a/src/bin/proxmox_backup_manager/openid.rs b/src/bin/proxmox_backup_manager/openid.rs
> new file mode 100644
> index 00000000..13915339
> --- /dev/null
> +++ b/src/bin/proxmox_backup_manager/openid.rs
> @@ -0,0 +1,99 @@
> +use anyhow::Error;
> +use serde_json::Value;
> +
> +use proxmox::api::{api, cli::*, RpcEnvironment, ApiHandler};
> +
> +use proxmox_backup::{config, api2, api2::types::REALM_ID_SCHEMA};
> +
> +
> +#[api(
> + input: {
> + properties: {
> + "output-format": {
> + schema: OUTPUT_FORMAT,
> + optional: true,
> + },
> + }
> + }
> +)]
> +/// List configured OpenId realms
> +fn list_openid_realms(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
> +
> + let output_format = get_output_format(¶m);
> +
> + let info = &api2::config::access::openid::API_METHOD_LIST_OPENID_REALMS;
> + let mut data = match info.handler {
> + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
> + _ => unreachable!(),
> + };
> +
> + let options = default_table_format_options()
> + .column(ColumnConfig::new("realm"))
> + .column(ColumnConfig::new("issuer-url"))
> + .column(ColumnConfig::new("comment"));
> +
> + format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
> +
> + Ok(Value::Null)
> +}
> +#[api(
> + input: {
> + properties: {
> + realm: {
> + schema: REALM_ID_SCHEMA,
> + },
> + "output-format": {
> + schema: OUTPUT_FORMAT,
> + optional: true,
> + },
> + }
> + }
> +)]
> +
> +/// Show OpenID realm configuration
> +fn show_openid_realm(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
> +
> + let output_format = get_output_format(¶m);
> +
> + let info = &api2::config::access::openid::API_METHOD_READ_OPENID_REALM;
> + let mut data = match info.handler {
> + ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
> + _ => unreachable!(),
> + };
> +
> + let options = default_table_format_options();
> + format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
> +
> + Ok(Value::Null)
> +}
> +
> +pub fn openid_commands() -> CommandLineInterface {
> +
> + let cmd_def = CliCommandMap::new()
> + .insert("list", CliCommand::new(&&API_METHOD_LIST_OPENID_REALMS))
> + .insert("show", CliCommand::new(&&API_METHOD_SHOW_OPENID_REALM)
> + .arg_param(&["realm"])
> + .completion_cb("realm", config::domains::complete_openid_realm_name)
> + )
> + .insert("create",
> + CliCommand::new(&api2::config::access::openid::API_METHOD_CREATE_OPENID_REALM)
> + .arg_param(&["realm"])
> + .arg_param(&["realm"])
^ this line is duplicated
> + .completion_cb("realm", config::domains::complete_openid_realm_name)
> + )
> + .insert("update",
> + CliCommand::new(&api2::config::access::openid::API_METHOD_UPDATE_OPENID_REALM)
> + .arg_param(&["realm"])
> + .arg_param(&["realm"])
^ this line is duplicated
> + .completion_cb("realm", config::domains::complete_openid_realm_name)
> + )
> + .insert("delete",
> + CliCommand::new(&api2::config::access::openid::API_METHOD_DELETE_OPENID_REALM)
> + .arg_param(&["realm"])
> + .arg_param(&["realm"])
^ this line is duplicated
> + .completion_cb("realm", config::domains::complete_openid_realm_name)
> + )
> + ;
> +
> + cmd_def.into()
> +}
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
` (9 preceding siblings ...)
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 10/10] fix CachedUserInfo by using a shared memory version counter Dietmar Maurer
@ 2021-06-29 9:50 ` Fabian Grünbichler
10 siblings, 0 replies; 13+ messages in thread
From: Fabian Grünbichler @ 2021-06-29 9:50 UTC (permalink / raw)
To: Proxmox Backup Server development discussion
seems to work okay in general (and looks okay code-wise), some missing
pieces:
- proxmox-openid-rs prints some information that looks like debug (full
returned info from provider)
- user-attr in proxmox-backup like in PVE
- GUI: user adding/editing needs some adaptation for openid (e.g., removing a
user prints a warning about unknown realm, but removes it anyway,
adding a user has no realm selection and requires a password)
- GUI: realm adding/editing is missing altogether
the check_acl_path change could be split and folded into the patches
that first use that ACL path, but no hard feelings there..
the last patch probably requires some in-depth review, I only took a
cursory glance (and it could be ordered before the patch with the fixme
that it fixes)
On June 25, 2021 11:20 am, Dietmar Maurer wrote:
> This implements OpenID connect realms using the new
> "proxmox-openid-rs" crate.
>
> Note: The new src/tools/memcom.rs is from Wolfgang.
>
> Changes since v2:
> fix CachedUserInfo by using a shared memory version counter
>
> Changes since v1:
> - really fix commit message of first patch
> - change api endpoints (/access/openid/{login|auth-url})
> - merged all api implementation patches
>
> Changes since preview version (for Fabian):
> - fix commit message
> - reserve namen 'pam' and 'pbs'
> - fix 'make deb'
>
>
> Dietmar Maurer (10):
> depend on proxmox-openid-rs
> config: new domains.cfg to configure openid realm
> check_acl_path: add /access/domains and /access/openid
> add API to manage openid realms
> cli: add CLI to manage openid realms.
> implement new helper is_active_user_id()
> cleanup user/token is_active() check
> api: add openid redirect/login API
> ui: implement OpenId login
> fix CachedUserInfo by using a shared memory version counter
>
> Cargo.toml | 2 +
> src/api2/access.rs | 4 +-
> src/api2/access/domain.rs | 18 ++
> src/api2/access/openid.rs | 190 ++++++++++++++++
> src/api2/config/access/mod.rs | 8 +-
> src/api2/config/access/openid.rs | 274 +++++++++++++++++++++++
> src/bin/proxmox-backup-manager.rs | 1 +
> src/bin/proxmox_backup_manager/mod.rs | 2 +
> src/bin/proxmox_backup_manager/openid.rs | 99 ++++++++
> src/config.rs | 1 +
> src/config/acl.rs | 8 +-
> src/config/cached_user_info.rs | 48 ++--
> src/config/domains.rs | 173 ++++++++++++++
> src/config/user.rs | 38 ++++
> src/tools.rs | 3 +
> src/tools/memcom.rs | 159 +++++++++++++
> www/Application.js | 8 +-
> www/LoginView.js | 100 ++++++++-
> www/Utils.js | 8 +
> 19 files changed, 1115 insertions(+), 29 deletions(-)
> create mode 100644 src/api2/access/openid.rs
> create mode 100644 src/api2/config/access/openid.rs
> create mode 100644 src/bin/proxmox_backup_manager/openid.rs
> create mode 100644 src/config/domains.rs
> create mode 100644 src/tools/memcom.rs
>
> --
> 2.30.2
>
>
> _______________________________________________
> pbs-devel mailing list
> pbs-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
>
>
>
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2021-06-29 9:50 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-25 9:20 [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 01/10] depend on proxmox-openid-rs Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 02/10] config: new domains.cfg to configure openid realm Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 03/10] check_acl_path: add /access/domains and /access/openid Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 04/10] add API to manage openid realms Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 05/10] cli: add CLI " Dietmar Maurer
2021-06-25 11:41 ` Wolfgang Bumiller
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 06/10] implement new helper is_active_user_id() Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 07/10] cleanup user/token is_active() check Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 08/10] api: add openid redirect/login API Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 09/10] ui: implement OpenId login Dietmar Maurer
2021-06-25 9:20 ` [pbs-devel] [PATCH proxmox-backup v3 10/10] fix CachedUserInfo by using a shared memory version counter Dietmar Maurer
2021-06-29 9:50 ` [pbs-devel] [PATCH proxmox-backup v3 00/10] OpenID connect realms Fabian Grünbichler
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox