mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-31 05:13:55 +02:00
Compare commits
10 commits
542d4fd9ae
...
d3c3a71909
Author | SHA1 | Date | |
---|---|---|---|
|
d3c3a71909 | ||
|
f40679cd52 | ||
|
52824b58f1 | ||
|
48f7324080 | ||
|
791a182767 | ||
|
51a1e815b2 | ||
|
399e1d71b8 | ||
|
2c53ae77c5 | ||
|
3a3d3eb807 | ||
|
6184d645d2 |
|
@ -1,6 +1,6 @@
|
|||
## Author
|
||||
|
||||
- William Brown (Firstyear): william@blackhats.net.au
|
||||
- William Brown (Firstyear): <william@blackhats.net.au>
|
||||
|
||||
## Contributors
|
||||
|
||||
|
@ -44,6 +44,7 @@
|
|||
- adamcstephens
|
||||
- Chris Olstrom (colstrom)
|
||||
- Christopher-Robin (cebbinghaus)
|
||||
- Krzysztof Dajka (alteriks)
|
||||
- Fabian Kammel (datosh)
|
||||
- Andris Raugulis (arthepsy)
|
||||
- Jason (argonaut0)
|
||||
|
|
BIN
examples/media/kanidm_proxmox.png
Normal file
BIN
examples/media/kanidm_proxmox.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 70 KiB |
91
examples/proxmox.md
Normal file
91
examples/proxmox.md
Normal file
|
@ -0,0 +1,91 @@
|
|||
# Proxmox PVE/PBS
|
||||
|
||||
## Helpful links
|
||||
|
||||
- <https://pve.proxmox.com/wiki/User_Management>
|
||||
- <https://pve.proxmox.com/pve-docs/pve-admin-guide.html#pveum_openid>
|
||||
|
||||
## Proxmox OIDC limitation
|
||||
|
||||
As of December 2024, the OIDC implementation in Proxmox supports only authentication.
|
||||
Authorization has to be done manually.
|
||||
Mapping user to specific groups won't work yet (steps 2,3,4).
|
||||
|
||||
Patch for this feature exists, but it hasn't been tested extensively:
|
||||
<https://lore.proxmox.com/pve-devel/20240901165512.687801-1-thomas@atskinner.net/>
|
||||
See also:
|
||||
<https://forum.proxmox.com/threads/openid-connect-default-group.103394/>
|
||||
|
||||
## On Kanidm
|
||||
|
||||
### 1. Create the proxmox resource server and configure the redirect URL
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 create proxmox "proxmox" https://yourproxmox.example.com
|
||||
kanidm system oauth2 add-redirect-url "proxmox" https://yourproxmox.example.com
|
||||
```
|
||||
|
||||
### 2. Create the appropriate group(s)
|
||||
|
||||
```bash
|
||||
kanidm group create proxmox_users --name idm_admin
|
||||
kanidm group create proxmox_admins --name idm_admin
|
||||
```
|
||||
|
||||
### 3. Add the appropriate users to the group
|
||||
|
||||
```bash
|
||||
kanidm group add-members proxmox_users user.name
|
||||
kanidm group add-members proxmox_admins user.name
|
||||
```
|
||||
|
||||
### 4. scope map
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 update-claim-map-join 'proxmox' 'proxmox_role' array
|
||||
kanidm system oauth2 update-claim-map 'proxmox' 'proxmox_role' 'proxmox_admins' 'admin'
|
||||
kanidm system oauth2 update-claim-map 'proxmox' 'proxmox_role' 'proxmox_users' 'user'
|
||||
```
|
||||
|
||||
### 5. Add the scopes
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 update-scope-map proxmox proxmox_users email profile openid
|
||||
```
|
||||
|
||||
### 6. Get the client secret
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 show-basic-secret proxmox
|
||||
```
|
||||
|
||||
Copy the value that is returned.
|
||||
|
||||
## On proxmox server
|
||||
|
||||
### Using WebGUI
|
||||
|
||||
Go to <https://yourproxmox.example.com>
|
||||
Select Datacenter->Realms->Add->OpenID Connect Server
|
||||

|
||||
Issuer URL:
|
||||
|
||||
- <https://idm.example.com:8443/oauth2/openid/proxmox>
|
||||
When kanidm is behind reverse proxy or when using docker port mapping:
|
||||
- <https://idm.example.com/oauth2/openid/proxmox>
|
||||
|
||||
Realm: give some proper name or anything that's meaningful
|
||||
|
||||
Client ID: name given in step 1 (resource server)
|
||||
|
||||
Client Key: secret from step 6
|
||||
|
||||
Autocreate Users: Automatically create users if they do not exist. Users are stored in Proxmox Cluster File System (pmxcfs) - /etc/pve/user.cfg
|
||||
|
||||
### Using CLI
|
||||
|
||||
Login to proxmox node and execute:
|
||||
|
||||
```bash
|
||||
pveum realm add kanidm --type openid --issuer-url https://idm.example.com/oauth2/openid/proxmox --client-id proxmox --client-key="secret from step 6" --username-claim username --scopes="email profile openid" --autocreate
|
||||
```
|
|
@ -94,7 +94,7 @@ pub struct KanidmClientConfigInstance {
|
|||
pub verify_hostnames: Option<bool>,
|
||||
/// Whether to verify the Certificate Authority details of the server's TLS certificate, defaults to `true`.
|
||||
///
|
||||
/// Environment variable is slightly inverted - `KANIDM_SKIP_HOSTNAME_VERIFICATION`.
|
||||
/// Environment variable is slightly inverted - `KANIDM_ACCEPT_INVALID_CERTS`.
|
||||
pub verify_ca: Option<bool>,
|
||||
/// Optionally you can specify the path of a CA certificate to use for verifying the server, if you're not using one trusted by your system certificate store.
|
||||
///
|
||||
|
@ -453,6 +453,13 @@ impl KanidmClientBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_token_cache_path(self, token_cache_path: Option<String>) -> Self {
|
||||
KanidmClientBuilder {
|
||||
token_cache_path,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn add_root_certificate_filepath(self, ca_path: &str) -> Result<Self, ClientError> {
|
||||
//Okay we have a ca to add. Let's read it in and setup.
|
||||
|
|
|
@ -662,9 +662,13 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
// Test 389ds formats
|
||||
// Test 389ds/openldap formats. Shout outs openldap which sometimes makes these
|
||||
// lowercase.
|
||||
|
||||
if let Some(ds_ssha1) = value.strip_prefix("{SHA}") {
|
||||
if let Some(ds_ssha1) = value
|
||||
.strip_prefix("{SHA}")
|
||||
.or_else(|| value.strip_prefix("{sha}"))
|
||||
{
|
||||
let h = general_purpose::STANDARD.decode(ds_ssha1).map_err(|_| ())?;
|
||||
if h.len() != DS_SHA1_HASH_LEN {
|
||||
return Err(());
|
||||
|
@ -674,7 +678,10 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ds_ssha1) = value.strip_prefix("{SSHA}") {
|
||||
if let Some(ds_ssha1) = value
|
||||
.strip_prefix("{SSHA}")
|
||||
.or_else(|| value.strip_prefix("{ssha}"))
|
||||
{
|
||||
let sh = general_purpose::STANDARD.decode(ds_ssha1).map_err(|_| ())?;
|
||||
let (h, s) = sh.split_at(DS_SHA1_HASH_LEN);
|
||||
if s.len() != DS_SHA_SALT_LEN {
|
||||
|
@ -685,7 +692,10 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ds_ssha256) = value.strip_prefix("{SHA256}") {
|
||||
if let Some(ds_ssha256) = value
|
||||
.strip_prefix("{SHA256}")
|
||||
.or_else(|| value.strip_prefix("{sha256}"))
|
||||
{
|
||||
let h = general_purpose::STANDARD
|
||||
.decode(ds_ssha256)
|
||||
.map_err(|_| ())?;
|
||||
|
@ -697,7 +707,10 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ds_ssha256) = value.strip_prefix("{SSHA256}") {
|
||||
if let Some(ds_ssha256) = value
|
||||
.strip_prefix("{SSHA256}")
|
||||
.or_else(|| value.strip_prefix("{ssha256}"))
|
||||
{
|
||||
let sh = general_purpose::STANDARD
|
||||
.decode(ds_ssha256)
|
||||
.map_err(|_| ())?;
|
||||
|
@ -710,7 +723,10 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ds_ssha512) = value.strip_prefix("{SHA512}") {
|
||||
if let Some(ds_ssha512) = value
|
||||
.strip_prefix("{SHA512}")
|
||||
.or_else(|| value.strip_prefix("{sha512}"))
|
||||
{
|
||||
let h = general_purpose::STANDARD
|
||||
.decode(ds_ssha512)
|
||||
.map_err(|_| ())?;
|
||||
|
@ -722,7 +738,10 @@ impl TryFrom<&str> for Password {
|
|||
});
|
||||
}
|
||||
|
||||
if let Some(ds_ssha512) = value.strip_prefix("{SSHA512}") {
|
||||
if let Some(ds_ssha512) = value
|
||||
.strip_prefix("{SSHA512}")
|
||||
.or_else(|| value.strip_prefix("{ssha512}"))
|
||||
{
|
||||
let sh = general_purpose::STANDARD
|
||||
.decode(ds_ssha512)
|
||||
.map_err(|_| ())?;
|
||||
|
@ -1441,8 +1460,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_sha1() {
|
||||
let im_pw = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{sha}W6ph5Mm5Pz8GgiULbPgzG37mj9g=";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
@ -1451,8 +1474,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_ssha1() {
|
||||
let im_pw = "{SSHA}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{ssha}EyzbBiP4u4zxOrLpKTORI/RX3HC6TCTJtnVOCQ==";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
@ -1461,8 +1488,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_sha256() {
|
||||
let im_pw = "{SHA256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{sha256}XohImNooBHFR0OVvjcYpJ3NgPQ1qq73WKhHvch0VQtg=";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
@ -1471,8 +1502,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_ssha256() {
|
||||
let im_pw = "{SSHA256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{ssha256}luYWfFJOZgxySTsJXHgIaCYww4yMpu6yest69j/wO5n5OycuHFV/GQ==";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
@ -1481,8 +1516,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_sha512() {
|
||||
let im_pw = "{SHA512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
@ -1491,8 +1530,12 @@ mod tests {
|
|||
#[test]
|
||||
fn test_password_from_ds_ssha512() {
|
||||
let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
|
||||
let _r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
// Known weak, require upgrade.
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
|
|
|
@ -91,6 +91,18 @@ impl CommonOpt {
|
|||
false => client_builder,
|
||||
};
|
||||
|
||||
let client_builder = match self.accept_invalid_certs {
|
||||
true => {
|
||||
warn!(
|
||||
"TLS Certificate Verification disabled!!! This can lead to credential and account compromise!!!"
|
||||
);
|
||||
client_builder.danger_accept_invalid_certs(true)
|
||||
}
|
||||
false => client_builder,
|
||||
};
|
||||
|
||||
let client_builder = client_builder.set_token_cache_path(self.token_cache_path.clone());
|
||||
|
||||
client_builder.build().unwrap_or_else(|e| {
|
||||
error!("Failed to build client instance -- {:?}", e);
|
||||
std::process::exit(1);
|
||||
|
|
|
@ -87,6 +87,13 @@ pub struct CommonOpt {
|
|||
default_value_t = false
|
||||
)]
|
||||
skip_hostname_verification: bool,
|
||||
/// Don't verify CA
|
||||
#[clap(
|
||||
long = "accept-invalid-certs",
|
||||
env = "KANIDM_ACCEPT_INVALID_CERTS",
|
||||
default_value_t = false
|
||||
)]
|
||||
accept_invalid_certs: bool,
|
||||
/// Path to a file to cache tokens in, defaults to ~/.cache/kanidm_tokens
|
||||
#[clap(short, long, env = "KANIDM_TOKEN_CACHE_PATH", hide = true, default_value = None,
|
||||
value_parser = clap::builder::NonEmptyStringValueParser::new())]
|
||||
|
|
Loading…
Reference in a new issue