Compare commits

...

17 commits

Author SHA1 Message Date
alteriks 634572caa8
Merge 48f7324080 into 97952d5490 2025-05-13 17:32:58 +08:00
Firstyear 97952d5490
Dont specify config path in container () 2025-05-13 14:30:38 +10:00
Firstyear 6a85e2a21b
Accept SSHA with different salt lengths () 2025-05-13 03:19:12 +00:00
James Hodgkinson 1774f9428c
Bye poetry, hi uv for python things ()
* fix: moving from poetry to uv for python packaging
* fix: updating rlm_python to use uv for things
2025-05-13 02:59:05 +00:00
Firstyear b7eda62e3b
Resolve flaw with ssh key parse if the key has no comment () 2025-05-13 02:39:10 +00:00
Firstyear b5cdf9dcf2
Indicate that this is an ip list, not a range ()
* Indicate that this is an ip list, not a range

We mistakenly commented that this was a range, not a list. This
has led to some confusion. Be clear it's a list of ip's, not a range.

* Support Ip Ranges instead of Ip Addresses in X-Forward-For

* Docs feedback
2025-05-13 01:53:58 +00:00
James Hodgkinson 47b091cd49
Test for corrupted unicode in SSH keys, keep the key title on error/resubmit () 2025-05-13 00:38:32 +00:00
James Hodgkinson 8daeddb9e7
Reduce replication logging verbosity 2025-05-13 10:21:47 +10:00
dependabot[bot] 1a39c5f5a2
Bump the all group across 1 directory with 7 updates ()
Bumps the all group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.5.37` | `4.5.38` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.48` | `4.5.50` |
| [rustls](https://github.com/rustls/rustls) | `0.23.26` | `0.23.27` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.19.1` | `3.20.0` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.44.2` | `1.45.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.21` | `1.2.22` |
| [tower-http](https://github.com/tower-rs/tower-http) | `0.6.2` | `0.6.4` |



Updates `clap` from 4.5.37 to 4.5.38
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.37...clap_complete-v4.5.38)

Updates `clap_complete` from 4.5.48 to 4.5.50
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.48...clap_complete-v4.5.50)

Updates `rustls` from 0.23.26 to 0.23.27
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.26...v/0.23.27)

Updates `tempfile` from 3.19.1 to 3.20.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.19.1...v3.20.0)

Updates `tokio` from 1.44.2 to 1.45.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.44.2...tokio-1.45.0)

Updates `cc` from 1.2.21 to 1.2.22
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.21...cc-v1.2.22)

Updates `tower-http` from 0.6.2 to 0.6.4
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.6.2...tower-http-0.6.4)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: clap_complete
  dependency-version: 4.5.50
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: rustls
  dependency-version: 0.23.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: tempfile
  dependency-version: 3.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: tokio
  dependency-version: 1.45.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: cc
  dependency-version: 1.2.22
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: tower-http
  dependency-version: 0.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 01:59:07 +00:00
dependabot[bot] 5a6f4fdb22
Bump the all group in /pykanidm with 2 updates ()
Bumps the all group in /pykanidm with 2 updates: [ruff](https://github.com/astral-sh/ruff) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `ruff` from 0.11.8 to 0.11.9
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.11.8...0.11.9)

Updates `mkdocs-material` from 9.6.12 to 9.6.13
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.12...9.6.13)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.11.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: mkdocs-material
  dependency-version: 9.6.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-12 11:35:08 +10:00
Krzysztof Dajka 48f7324080 Merge branch 'master' of github.com:alteriks/kanidm 2025-02-11 11:50:55 +01:00
Krzysztof Dajka 791a182767 NEW: proxmox example docs
Add redirect URL to proxmox documentation

Update contributors list

Update examples/proxmox.md
2025-02-11 11:49:57 +01:00
alteriks 51a1e815b2
Merge branch 'master' into master 2025-02-11 09:55:56 +01:00
James Hodgkinson 399e1d71b8
Update examples/proxmox.md 2025-02-11 07:26:44 +10:00
Krzysztof Dajka 2c53ae77c5 Update contributors list 2024-12-05 09:54:51 +01:00
Krzysztof Dajka 3a3d3eb807 Add redirect URL to proxmox documentation 2024-12-05 09:54:21 +01:00
Krzysztof Dajka 6184d645d2 NEW: proxmox example 2024-12-04 21:42:34 +01:00
54 changed files with 3949 additions and 2861 deletions

View file

@ -63,10 +63,9 @@ jobs:
mv ./target/doc/* ./docs/${{ inputs.tag }}/rustdoc/
- name: pykanidm docs
run: |
python -m pip install poetry
python -m pip install uv
cd pykanidm
poetry install
poetry run mkdocs build
uv run --group docs mkdocs build
cd ..
mv pykanidm/site ./docs/${{ inputs.tag }}/pykanidm
continue-on-error: true

View file

@ -10,32 +10,30 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
python_tests:
runs-on: ubuntu-latest
env:
UV_LINK_MODE: "copy"
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Install poetry
run: pipx install poetry
- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'poetry'
- name: Install uv
run: pip install --root-user-action=ignore uv
- name: Running mypy
run: |
cd pykanidm
python --version
poetry install
poetry run mypy --strict kanidm tests
uv run mypy --strict kanidm tests
uv run ty check
- name: Running Linting
run: |
cd pykanidm
poetry install
poetry run ruff check kanidm tests
uv run ruff check kanidm tests
- name: Running pytest
run: |
cd pykanidm
poetry install
poetry run pytest -v -m 'not network'
uv run pytest -v -m 'not network'

View file

@ -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)

346
Cargo.lock generated
View file

@ -19,16 +19,16 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
"getrandom 0.2.16",
"getrandom 0.3.3",
"once_cell",
"serde",
"version_check",
"zerocopy 0.7.35",
"zerocopy",
]
[[package]]
@ -425,9 +425,9 @@ dependencies = [
[[package]]
name = "backtrace"
version = "0.3.74"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [
"addr2line",
"cfg-if",
@ -652,9 +652,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.21"
version = "1.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0"
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
dependencies = [
"shlex",
]
@ -695,6 +695,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "cidr"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd1b64030216239a2e7c364b13cd96a2097ebf0dfe5025f2dedee14a23f2ab60"
dependencies = [
"serde",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
@ -708,9 +717,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.37"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071"
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
dependencies = [
"clap_builder",
"clap_derive",
@ -718,9 +727,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.37"
version = "4.5.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2"
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
dependencies = [
"anstream",
"anstyle",
@ -730,9 +739,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.48"
version = "4.5.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8c97f3a6f02b9e24cadc12aaba75201d18754b53ea0a9d99642f806ccdb4c9"
checksum = "c91d3baa3bcd889d60e6ef28874126a0b384fd225ab83aa6d8a801c519194ce1"
dependencies = [
"clap",
]
@ -1671,9 +1680,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"js-sys",
@ -1750,7 +1759,7 @@ dependencies = [
"gix-utils 0.2.0",
"itoa",
"thiserror 2.0.12",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -1806,7 +1815,7 @@ dependencies = [
"smallvec",
"thiserror 2.0.12",
"unicode-bom",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -1996,7 +2005,7 @@ dependencies = [
"itoa",
"smallvec",
"thiserror 2.0.12",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -2052,9 +2061,9 @@ dependencies = [
[[package]]
name = "gix-path"
version = "0.10.17"
version = "0.10.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c091d2e887e02c3462f52252c5ea61150270c0f2657b642e8d0d6df56c16e642"
checksum = "567f65fec4ef10dfab97ae71f26a27fd4d7fe7b8e3f90c8a58551c41ff3fb65b"
dependencies = [
"bstr",
"gix-trace",
@ -2080,7 +2089,7 @@ dependencies = [
"gix-utils 0.2.0",
"maybe-async",
"thiserror 2.0.12",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -2112,7 +2121,7 @@ dependencies = [
"gix-validate 0.9.4",
"memmap2",
"thiserror 2.0.12",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -2316,9 +2325,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633"
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
dependencies = [
"atomic-waker",
"bytes",
@ -2552,7 +2561,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.9",
"h2 0.4.10",
"http 1.3.1",
"http-body 1.0.1",
"httparse",
@ -2580,7 +2589,7 @@ dependencies = [
"tokio",
"tokio-rustls",
"tower-service",
"webpki-roots",
"webpki-roots 0.26.11",
]
[[package]]
@ -2658,21 +2667,22 @@ dependencies = [
[[package]]
name = "icu_collections"
version = "1.5.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
name = "icu_locale_core"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
dependencies = [
"displaydoc",
"litemap",
@ -2681,31 +2691,11 @@ dependencies = [
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
dependencies = [
"displaydoc",
"icu_collections",
@ -2713,67 +2703,54 @@ dependencies = [
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7"
checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "1.5.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"tinystr",
"potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2"
checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
[[package]]
name = "icu_provider"
version = "1.5.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"icu_locale_core",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@ -2804,9 +2781,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
@ -2918,9 +2895,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.11"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27e77966151130221b079bcec80f1f34a9e414fa489d99152a201c07fd2182bc"
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
@ -2933,9 +2910,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.11"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97265751f8a9a4228476f2fc17874a9e7e70e96b893368e42619880fe143b48a"
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
dependencies = [
"proc-macro2",
"quote",
@ -3274,6 +3251,7 @@ dependencies = [
"axum-macros",
"bytes",
"chrono",
"cidr",
"compact_jwt",
"cron",
"filetime",
@ -3383,6 +3361,7 @@ dependencies = [
name = "kanidmd_testkit"
version = "1.7.0-dev"
dependencies = [
"cidr",
"compact_jwt",
"escargot",
"fantoccini",
@ -3418,9 +3397,9 @@ dependencies = [
[[package]]
name = "kqueue"
version = "1.0.8"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
dependencies = [
"kqueue-sys",
"libc",
@ -3438,9 +3417,9 @@ dependencies = [
[[package]]
name = "lambert_w"
version = "1.2.17"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc66ddcab7f8a3cc035052b0bb1f9f7f47ac92741b3fe78974bdd356fe023a40"
checksum = "a3269cd75481b02173ffe6cb30f08e3eae78b20eb2ed6bfbdb3ce2a90446d83f"
dependencies = [
"num-complex",
"num-traits",
@ -3514,19 +3493,19 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.53.0",
]
[[package]]
name = "libm"
version = "0.2.13"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libmimalloc-sys"
@ -3604,9 +3583,9 @@ checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.7.5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
[[package]]
name = "litrs"
@ -3660,6 +3639,12 @@ dependencies = [
"hashbrown 0.15.3",
]
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "malloced"
version = "1.3.1"
@ -3694,9 +3679,9 @@ dependencies = [
[[package]]
name = "matrixmultiply"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"num_cpus",
@ -3853,9 +3838,9 @@ dependencies = [
[[package]]
name = "nix"
version = "0.30.0"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537bc3c4a347b87fd52ac6c03a02ab1302962cfd93373c5d7a112cdc337854cc"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.0",
"cfg-if",
@ -4531,6 +4516,15 @@ dependencies = [
"portable-atomic",
]
[[package]]
name = "potential_utf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
dependencies = [
"zerovec",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -4543,7 +4537,7 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy 0.8.25",
"zerocopy",
]
[[package]]
@ -4674,9 +4668,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quinn"
version = "0.11.7"
version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012"
checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8"
dependencies = [
"bytes",
"cfg_aliases",
@ -4694,12 +4688,13 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.11"
version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
dependencies = [
"bytes",
"getrandom 0.3.2",
"getrandom 0.3.3",
"lru-slab",
"rand 0.9.1",
"ring",
"rustc-hash 2.1.1",
@ -4797,7 +4792,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.2",
"getrandom 0.3.3",
]
[[package]]
@ -4808,9 +4803,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "redox_syscall"
version = "0.5.11"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.0",
]
@ -4954,7 +4949,7 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2 0.4.9",
"h2 0.4.10",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
@ -4987,7 +4982,7 @@ dependencies = [
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"webpki-roots 0.26.11",
"windows-registry",
]
@ -5046,9 +5041,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "8.7.0"
version = "8.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5fbc0ee50fcb99af7cebb442e5df7b5b45e9460ffa3f8f549cd26b862bec49d"
checksum = "60e425e204264b144d4c929d126d0de524b40a961686414bab5040f7465c71be"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@ -5133,9 +5128,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321"
dependencies = [
"once_cell",
"ring",
@ -5168,18 +5163,19 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c"
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
dependencies = [
"web-time",
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.1"
version = "0.103.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03"
checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
dependencies = [
"ring",
"rustls-pki-types",
@ -5630,7 +5626,7 @@ dependencies = [
[[package]]
name = "sshkeys"
version = "0.3.3"
source = "git+https://github.com/Firstyear/rust-sshkeys.git?rev=3a081cbf7480628223bcb96fc8aaa8c19109d007#3a081cbf7480628223bcb96fc8aaa8c19109d007"
source = "git+https://github.com/Firstyear/rust-sshkeys.git?rev=49cb53232115d3aea86cd059b151e376293805fc#49cb53232115d3aea86cd059b151e376293805fc"
dependencies = [
"base64 0.22.1",
"byteorder",
@ -5745,12 +5741,12 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tempfile"
version = "3.19.1"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom 0.3.2",
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.7",
"windows-sys 0.59.0",
@ -5865,9 +5861,9 @@ dependencies = [
[[package]]
name = "tinystr"
version = "0.7.6"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
dependencies = [
"displaydoc",
"zerovec",
@ -5911,9 +5907,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.44.2"
version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [
"backtrace",
"bytes",
@ -6026,7 +6022,7 @@ dependencies = [
"serde_spanned",
"toml_datetime",
"toml_write",
"winnow 0.7.9",
"winnow 0.7.10",
]
[[package]]
@ -6046,7 +6042,7 @@ dependencies = [
"axum",
"base64 0.22.1",
"bytes",
"h2 0.4.9",
"h2 0.4.10",
"http 1.3.1",
"http-body 1.0.1",
"http-body-util",
@ -6104,9 +6100,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"async-compression",
"bitflags 2.9.0",
@ -6347,12 +6343,6 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
@ -6414,7 +6404,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [
"getrandom 0.3.2",
"getrandom 0.3.3",
"serde",
]
@ -6707,9 +6697,18 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.26.10"
version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
"webpki-roots 1.0.0",
]
[[package]]
name = "webpki-roots"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb"
dependencies = [
"rustls-pki-types",
]
@ -7133,9 +7132,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.7.9"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
@ -7159,17 +7158,11 @@ dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
[[package]]
name = "x509-cert"
@ -7202,9 +7195,9 @@ dependencies = [
[[package]]
name = "yoke"
version = "0.7.5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
dependencies = [
"serde",
"stable_deref_trait",
@ -7214,9 +7207,9 @@ dependencies = [
[[package]]
name = "yoke-derive"
version = "0.7.5"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
dependencies = [
"proc-macro2",
"quote",
@ -7224,33 +7217,13 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
dependencies = [
"zerocopy-derive 0.8.25",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"zerocopy-derive",
]
[[package]]
@ -7306,10 +7279,21 @@ dependencies = [
]
[[package]]
name = "zerovec"
version = "0.10.4"
name = "zerotrie"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
dependencies = [
"yoke",
"zerofrom",
@ -7318,9 +7302,9 @@ dependencies = [
[[package]]
name = "zerovec-derive"
version = "0.10.3"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
dependencies = [
"proc-macro2",
"quote",

View file

@ -121,7 +121,7 @@ codegen-units = 256
# For BSD nss support
libnss = { git = "https://github.com/Firstyear/libnss-rs.git", branch = "20250207-freebsd" }
# Allow ssh keys to have comments with spaces.
sshkeys = { git = "https://github.com/Firstyear/rust-sshkeys.git", rev = "3a081cbf7480628223bcb96fc8aaa8c19109d007" }
sshkeys = { git = "https://github.com/Firstyear/rust-sshkeys.git", rev = "49cb53232115d3aea86cd059b151e376293805fc" }
# Branch currently carrying some needed rs256 signer patches for jwk handling,
# as main is currently working to drop openssl and may need more work before
# we commit to that change here.
@ -166,8 +166,9 @@ base64 = "^0.22.1"
base64urlsafedata = "0.5.1"
bitflags = "^2.8.0"
bytes = "^1.9.0"
clap = { version = "4.5.37", features = ["derive", "env"] }
clap_complete = "^4.5.42"
cidr = "0.3.1"
clap = { version = "4.5.38", features = ["derive", "env"] }
clap_complete = "^4.5.50"
# Forced by saffron/cron
chrono = "^0.4.39"
compact_jwt = { version = "^0.4.2", default-features = false }
@ -254,7 +255,7 @@ reqwest = { version = "0.12.12", default-features = false, features = [
"rustls-tls-native-roots-no-provider",
] }
rusqlite = { version = "0.35.0", features = ["array", "bundled"] }
rustls = { version = "0.23.26", default-features = false, features = [
rustls = { version = "0.23.27", default-features = false, features = [
"aws_lc_rs",
] }
@ -274,11 +275,11 @@ sshkey-attest = "^0.5.0"
sshkeys = "0.3.3"
svg = "0.18.0"
syn = { version = "2.0.100", features = ["full"] }
tempfile = "3.15.0"
tempfile = "3.20.0"
testkit-macros = { path = "./server/testkit-macros" }
time = { version = "^0.3.36", features = ["formatting", "local-offset"] }
tokio = "^1.44.2"
tokio = "^1.45.0"
tokio-openssl = "^0.6.5"
tokio-util = "^0.7.13"

View file

@ -170,7 +170,7 @@ codespell:
codespell -c \
-D .codespell_dictionary \
--ignore-words .codespell_ignore \
--skip='./target,./pykanidm/.venv,./pykanidm/.mypy_cache,./.mypy_cache,./pykanidm/poetry.lock' \
--skip='./target,./pykanidm/.venv,./pykanidm/.mypy_cache,./.mypy_cache,./pykanidm/uv.lock' \
--skip='./book/*.js' \
--skip='./book/book/*' \
--skip='./book/src/images/*' \
@ -184,21 +184,17 @@ codespell:
.PHONY: test/pykanidm/pytest
test/pykanidm/pytest: ## python library testing
cd pykanidm && \
poetry install && \
poetry run pytest -vv
uv run pytest -vv
.PHONY: test/pykanidm/lint
test/pykanidm/lint: ## python library linting
cd pykanidm && \
poetry install && \
poetry run ruff check tests kanidm
uv run ruff check tests kanidm
.PHONY: test/pykanidm/mypy
test/pykanidm/mypy: ## python library type checking
cd pykanidm && \
poetry install && \
echo "Running mypy" && \
poetry run mypy --strict tests kanidm
uv run mypy --strict tests kanidm
.PHONY: test/pykanidm
test/pykanidm: ## run the kanidm python module test suite (mypy/lint/pytest)
@ -261,15 +257,13 @@ clean_book:
docs/pykanidm/build: ## Build the mkdocs
docs/pykanidm/build:
cd pykanidm && \
poetry install && \
poetry run mkdocs build
uv run --group docs mkdocs build
.PHONY: docs/pykanidm/serve
docs/pykanidm/serve: ## Run the local mkdocs server
docs/pykanidm/serve:
cd pykanidm && \
poetry install && \
poetry run mkdocs serve
uv run --group docs mkdocs serve
########################################################################
@ -368,3 +362,11 @@ publish:
cargo publish -p kanidm_client
cargo publish -p kanidm_tools
.PHONY: rust_container
rust_container: # Build and run a container based on the Linux rust base container, with our requirements included
rust_container:
docker build --pull -t kanidm_rust -f scripts/Dockerfile.devcontainer .
docker run \
--rm -it \
--name kanidm \
--mount type=bind,source=$(PWD),target=/kanidm -w /kanidm kanidm_rust:latest

View file

@ -11,13 +11,13 @@ So far it includes:
TODO: a lot of things.
## Setting up your dev environment.
## Setting up your dev environment
Setting up a dev environment can be a little complex because of the mono-repo.
1. Install poetry: `python -m pip install poetry`. This is what we use to manage the packages, and
1. Install uv: `python -m pip install uv`. This is what we use to manage the packages, and
allows you to set up virtual python environments easier.
2. Build the base environment. From within the `pykanidm` directory, run: `poetry install` This'll
2. Build the base environment. From within the `pykanidm` directory, run: `uv sync` This'll
set up a virtual environment and install all the required packages (and development-related ones)
3. Start editing!

View file

@ -2,10 +2,10 @@
Setting up a dev environment has some extra complexity due to the mono-repo design.
1. Install poetry: `python -m pip install poetry`. This is what we use to manage the packages, and
1. Install uv: `python -m pip install uv`. This is what we use to manage the packages, and
allows you to set up virtual python environments easier.
2. Build the base environment. From within the kanidm_rlm_python directory, run: `poetry install`
3. Install the `kanidm` python library: `poetry run python -m pip install ../pykanidm`
2. Build the base environment. From within the kanidm_rlm_python directory, run: `uv sync`
3. Install the `kanidm` python library: `uv run python -m pip install ../pykanidm`
4. Start editing!
Most IDEs will be happier if you open the `kanidm_rlm_python` or `pykanidm` directories as the base

Binary file not shown.

After

(image error) Size: 70 KiB

91
examples/proxmox.md Normal file
View 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
![](media/kanidm_proxmox.png)
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
```

View file

@ -82,25 +82,25 @@ origin = "https://idm.example.com:8443"
# will often add a header such as "Forwarded" or
# "X-Forwarded-For". Some other proxies can use the PROXY
# protocol v2 header.
# This setting allows configuration of the range of trusted
# IPs which can supply this header information, and which
# format the information is provided in.
# This setting allows configuration of the list of trusted
# IPs or IP ranges which can supply this header information,
# and which format the information is provided in.
# Defaults to "none" (no trusted sources)
# Only one option can be used at a time.
# [http_client_address_info]
# proxy-v2 = ["127.0.0.1"]
# proxy-v2 = ["127.0.0.1", "127.0.0.0/8"]
# # OR
# x-forward-for = ["127.0.0.1"]
# x-forward-for = ["127.0.0.1", "127.0.0.0/8"]
# LDAPS requests can be reverse proxied by a loadbalancer.
# To preserve the original IP of the caller, these systems
# can add a header such as the PROXY protocol v2 header.
# This setting allows configuration of the range of trusted
# IPs which can supply this header information, and which
# format the information is provided in.
# This setting allows configuration of the list of trusted
# IPs or IP ranges which can supply this header information,
# and which format the information is provided in.
# Defaults to "none" (no trusted sources)
# [ldap_client_address_info]
# proxy-v2 = ["127.0.0.1"]
# proxy-v2 = ["127.0.0.1", "127.0.0.0/8"]
[online_backup]
# The path to the output folder for online backups

View file

@ -81,25 +81,25 @@ origin = "https://idm.example.com:8443"
# will often add a header such as "Forwarded" or
# "X-Forwarded-For". Some other proxies can use the PROXY
# protocol v2 header.
# This setting allows configuration of the range of trusted
# IPs which can supply this header information, and which
# format the information is provided in.
# This setting allows configuration of the list of trusted
# IPs or IP ranges which can supply this header information,
# and which format the information is provided in.
# Defaults to "none" (no trusted sources)
# Only one option can be used at a time.
# [http_client_address_info]
# proxy-v2 = ["127.0.0.1"]
# proxy-v2 = ["127.0.0.1", "127.0.0.0/8"]
# # OR
# x-forward-for = ["127.0.0.1"]
# x-forward-for = ["127.0.0.1", "127.0.0.0/8"]
# LDAPS requests can be reverse proxied by a loadbalancer.
# To preserve the original IP of the caller, these systems
# can add a header such as the PROXY protocol v2 header.
# This setting allows configuration of the range of trusted
# IPs which can supply this header information, and which
# format the information is provided in.
# This setting allows configuration of the list of trusted
# IPs or IP ranges which can supply this header information,
# and which format the information is provided in.
# Defaults to "none" (no trusted sources)
# [ldap_client_address_info]
# proxy-v2 = ["127.0.0.1"]
# proxy-v2 = ["127.0.0.1", "127.0.0.0/8"]
[online_backup]
# The path to the output folder for online backups

View file

@ -52,7 +52,6 @@ const PBKDF2_KEY_LEN: usize = 32;
const PBKDF2_MIN_NIST_KEY_LEN: usize = 32;
const PBKDF2_SHA1_MIN_KEY_LEN: usize = 19;
const DS_SHA_SALT_LEN: usize = 8;
const DS_SHA1_HASH_LEN: usize = 20;
const DS_SHA256_HASH_LEN: usize = 32;
const DS_SHA512_HASH_LEN: usize = 64;
@ -618,10 +617,8 @@ impl TryFrom<&str> for Password {
.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 {
return Err(());
}
let (h, s) = sh.split_at_checked(DS_SHA1_HASH_LEN).ok_or(())?;
return Ok(Password {
material: Kdf::SSHA1(s.to_vec(), h.to_vec()),
});
@ -649,10 +646,8 @@ impl TryFrom<&str> for Password {
let sh = general_purpose::STANDARD
.decode(ds_ssha256)
.map_err(|_| ())?;
let (h, s) = sh.split_at(DS_SHA256_HASH_LEN);
if s.len() != DS_SHA_SALT_LEN {
return Err(());
}
let (h, s) = sh.split_at_checked(DS_SHA256_HASH_LEN).ok_or(())?;
return Ok(Password {
material: Kdf::SSHA256(s.to_vec(), h.to_vec()),
});
@ -680,10 +675,8 @@ impl TryFrom<&str> for Password {
let sh = general_purpose::STANDARD
.decode(ds_ssha512)
.map_err(|_| ())?;
let (h, s) = sh.split_at(DS_SHA512_HASH_LEN);
if s.len() != DS_SHA_SALT_LEN {
return Err(());
}
let (h, s) = sh.split_at_checked(DS_SHA512_HASH_LEN).ok_or(())?;
return Ok(Password {
material: Kdf::SSHA512(s.to_vec(), h.to_vec()),
});
@ -1404,9 +1397,15 @@ mod tests {
#[test]
fn test_password_from_ds_ssha512() {
// from #3615
let im_pw = "{SSHA512}SvpKVQPfDUw7DbVFLVdhFUj33qx2zwkCNyfdRUEvYTloJt15HDVfhHzx6HLaKFUPBOCa/6D8lDnrybYzW+xSQC2GXBvYpn3ScVEcC+oH20I=";
let _r = Password::try_from(im_pw).expect("Failed to parse");
// Valid hash to import
let im_pw = "{SSHA512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
let _r = Password::try_from(im_pw).expect("Failed to parse");
// allow lower case of the hash type
let im_pw = "{ssha512}JwrSUHkI7FTAfHRVR6KoFlSN0E3dmaQWARjZ+/UsShYlENOqDtFVU77HJLLrY2MuSp0jve52+pwtdVl2QUAHukQ0XUf5LDtM";
let password = "password";
let r = Password::try_from(im_pw).expect("Failed to parse");

View file

@ -15,14 +15,14 @@ python -m pip install kanidm
Documentation can be generated by [cloning the repository](https://github.com/kanidm/kanidm) and
running `make docs/pykanidm/build`. The documentation will appear in `./pykanidm/site`. You'll need
make and the [poetry](https://pypi.org/project/poetry/) package installed.
make and the [uv](https://pypi.org/project/uv/) package installed.
## Testing
Set up your dev environment using `poetry` - `python -m pip install poetry && poetry install`.
Set up your dev environment using `uv` - `python -m pip install uv && uv sync`.
Pytest it used for testing, if you don't have a live server to test against and config set up, use
`poetry run pytest -m 'not network'`.
`uv run pytest -m 'not network'`.
## Changelog
@ -31,3 +31,4 @@ Pytest it used for testing, if you don't have a live server to test against and
| 0.0.1 | 2022-08-16 | Initial release |
| 0.0.2 | 2022-08-16 | Updated license, including test code in package |
| 0.0.3 | 2022-08-17 | Updated test suite to allow skipping of network tests |
| 1.2.0 | 2025-05-13 | Replaced poetry with uv for packaging |

View file

@ -12,6 +12,7 @@ import ssl
from typing import Any, Dict, List, Optional, Tuple, Union
import aiohttp
import aiohttp.client
from pydantic import ValidationError
import yarl
@ -528,7 +529,7 @@ class KanidmClient:
response: ClientResponse[IOauth2Rs] = await self.call_get(endpoint)
if response.status_code != 200 or response.data is None:
raise ValueError(f"Failed to get oauth2 resource server: {response.content}")
return RawOAuth2Rs(**response.data).as_oauth2_rs
return RawOAuth2Rs.model_validate(response.data).as_oauth2_rs
async def oauth2_rs_secret_get(self, rs_name: str) -> str:
"""get an OAuth2 client secret"""
@ -588,7 +589,7 @@ class KanidmClient:
response: ClientResponse[IServiceAccount] = await self.call_get(endpoint)
if response.status_code != 200 or response.data is None:
raise ValueError(f"Failed to get service account: {response.content}")
return RawServiceAccount(**response.data).as_service_account
return RawServiceAccount.model_validate(response.data).as_service_account
async def service_account_create(self, name: str, displayname: str) -> ClientResponse[None]:
"""Create a service account"""
@ -675,7 +676,7 @@ class KanidmClient:
response: ClientResponse[IGroup] = await self.call_get(endpoint)
if response.status_code != 200 or response.data is None:
raise ValueError(f"Failed to get group: {response.content}")
return RawGroup(**response.data).as_group
return RawGroup.model_validate(response.data).as_group
async def group_create(self, name: str) -> ClientResponse[None]:
"""Create a group"""
@ -720,7 +721,7 @@ class KanidmClient:
response: ClientResponse[IPerson] = await self.call_get(endpoint)
if response.status_code != 200 or response.data is None:
raise ValueError(f"Failed to get person: {response.content}")
return RawPerson(**response.data).as_person
return RawPerson.model_validate(response.data).as_person
async def person_account_create(self, name: str, displayname: str) -> ClientResponse[None]:
"""Create a person account"""

View file

@ -49,13 +49,13 @@ def instantiate(_: Any) -> Any:
if config_path is None:
logging.error("Failed to find configuration file, checked (%s), quitting!", CONFIG_PATHS)
sys.exit(1)
kanidm_client = KanidmClient(config_file=config_path)
if kanidm_client.config.auth_token is None:
logging.error("You need to specify auth_token in the configuration file!")
sys.exit(1)
os.environ["KANIDM_CONFIG_FILE"] = config_path.as_posix()
logging.info("Config file: %s", config_path.as_posix())
else:
kanidm_client = KanidmClient(config_file=config_path)
if kanidm_client.config.auth_token is None:
logging.error("You need to specify auth_token in the configuration file!")
sys.exit(1)
os.environ["KANIDM_CONFIG_FILE"] = config_path.as_posix()
logging.info("Config file: %s", os.environ["KANIDM_CONFIG_FILE"])
return radiusd.RLM_MODULE_OK

2409
pykanidm/poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,21 @@
[tool.poetry]
[project]
authors = [{ name = "James Hodgkinson", email = "james@terminaloutcomes.com" }]
license = { text = "MPL-2.0" }
requires-python = "<4.0,>=3.9"
dependencies = [
"toml>=0.10.2",
"pydantic>=2.0.0",
"aiohttp>=3.8.1",
"Authlib>=1.2.0",
]
name = "kanidm"
version = "1.0.1"
description = "Kanidm client library"
license = "MPL-2.0"
authors = ["James Hodgkinson <james@terminaloutcomes.com>"]
version = "1.2.0"
readme = "README.md"
repository = "https://github.com/kanidm/kanidm"
homepage = "https://kanidm.com/"
packages = [{ include = "kanidm" }]
keywords = ["kanidm", "idm", "api"]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
@ -20,40 +23,47 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Operating System :: OS Independent",
]
[tool.poetry.dependencies]
python = "^3.9"
toml = "^0.10.2"
pydantic = ">=2.0.0,<3.0.0"
aiohttp = "^3.8.1"
Authlib = "^1.2.0"
[project.urls]
homepage = "https://kanidm.com/"
repository = "https://github.com/kanidm/kanidm"
[tool.poetry.group.dev.dependencies]
ruff = ">=0.5.1,<0.11.9"
pytest = "^8.3.4"
mypy = "^1.14.1"
types-requests = "^2.32.0.20241016"
pytest-aiohttp = "^1.1.0"
pytest-mock = "^3.14.0"
types-toml = "^0.10.8.20240310"
pylint-pydantic = "^0.3.5"
coverage = "^7.6.10"
mkdocs = "^1.6.1"
mkdocs-material = "^9.6.1"
mkdocstrings = ">=0.27,<0.30"
mkdocstrings-python = "^1.13.0"
pook = "^2.1.3"
[dependency-groups]
dev = [
"ruff>=0.5.1",
"pytest>=8.3.4",
"mypy>=1.14.1,<2.0.0",
"types-requests>=2.32.0.20241016",
"pytest-aiohttp>=1.1.0",
"pytest-mock>=3.14.0",
"types-toml>=0.10.8.20240310",
"pylint-pydantic>=0.3.5",
"coverage>=7.6.10",
"pook>=2.1.3",
"ty>=0.0.0a8",
]
docs = [
"mkdocs>=1.6.1",
"mkdocs-material>=9.6.13",
"mkdocstrings>=0.29.1",
"mkdocstrings-python>=1.16.10",
]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pylint.MASTER]
max-line-length = 150
disable = "W0511,raise-missing-from"
extension-pkg-allow-list = "pydantic"
# https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422
load-plugins = "pylint_pydantic,pylint_pytest"
[tool.pdm.build]
includes = ["kanidm"]
# [tool.pylint.MASTER]
# max-line-length = 150
# disable = "W0511,raise-missing-from"
# extension-pkg-allow-list = "pydantic"
# # https://github.com/samuelcolvin/pydantic/issues/1961#issuecomment-759522422
# load-plugins = "pylint_pydantic,pylint_pytest"
[tool.ruff]
line-length = 150
@ -64,9 +74,9 @@ line-length = 150
"F811", # pytest fixtures
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
markers = [
"network: Tests that require network access and a working backend server",
"interactive: Requires specific config and a working backend server",
@ -77,4 +87,6 @@ source = ["kanidm"]
omit = ["tests"]
[tool.mypy]
strict = true
plugins = "pydantic.mypy"
disable_error_code = "unused-ignore"

View file

@ -1,5 +1,5 @@
#!/bin/bash
poetry run coverage run -m pytest -vvx && \
poetry run coverage html
uv run coverage run -m pytest -vvx && \
uv run coverage html

View file

View file

@ -7,7 +7,7 @@ import pytest
from pytest_mock import MockerFixture
# pylint: disable=unused-import
from testutils import client, client_configfile, MockResponse
from .testutils import client, client_configfile, MockResponse
from kanidm import KanidmClient
from kanidm.exceptions import AuthCredFailed, AuthInitFailed
@ -25,9 +25,11 @@ async def test_auth_init(client_configfile: KanidmClient) -> None:
print("Starting client...")
print(f"Doing auth_init for {client_configfile.config.username}")
if client_configfile.config.username is None:
pytest.skip("Can't run auth test without a username/password")
result = await client_configfile.auth_init(client_configfile.config.username)
username = client_configfile.config.username
if username is None:
raise pytest.skip("Can't run auth test without a username/password") # type: ignore[call-non-callable]
result = await client_configfile.auth_init(username)
print(f"{result=}")
print(result.model_dump_json())
assert result.sessionid
@ -39,9 +41,11 @@ async def test_auth_begin(client_configfile: KanidmClient) -> None:
"""tests the auth begin step"""
print(f"Doing auth_init for {client_configfile.config.username}")
if client_configfile.config.username is None:
pytest.skip("Can't run auth test without a username/password")
result = await client_configfile.auth_init(client_configfile.config.username)
username = client_configfile.config.username
if username is None:
raise pytest.skip("Can't run auth test without a username/password") # type: ignore[call-non-callable]
result = await client_configfile.auth_init(username)
print(f"{result=}")
print("Result dict:")
print(result.model_dump_json())
@ -60,7 +64,7 @@ async def test_auth_begin(client_configfile: KanidmClient) -> None:
retval = begin_result.data
if retval is None:
raise pytest.fail("Failed to do begin_result")
raise pytest.fail("Failed to do begin_result") # type: ignore[call-non-callable]
retval["response"] = begin_result.model_dump()
@ -72,7 +76,7 @@ async def test_auth_begin(client_configfile: KanidmClient) -> None:
async def test_authenticate_flow(client_configfile: KanidmClient) -> None:
"""tests the authenticate() flow"""
if client_configfile.config.username is None or client_configfile.config.password is None:
pytest.skip("Can't run this without a username and password set in the config file")
pytest.skip("Can't run this without a username and password set in the config file") # type: ignore[call-non-callable]
client_configfile.config.auth_token = None
print(f"Doing client.authenticate for {client_configfile.config.username}")
@ -96,10 +100,10 @@ async def test_authenticate_anonymous(client_configfile: KanidmClient) -> None:
async def test_authenticate_flow_fail(client_configfile: KanidmClient) -> None:
"""tests the authenticate() flow with a valid (hopefully) username and invalid password"""
if not bool(os.getenv("RUN_SCARY_TESTS", None)):
pytest.skip(reason="Skipping because env var RUN_SCARY_TESTS isn't set")
pytest.skip("Skipping because env var RUN_SCARY_TESTS isn't set") # type: ignore[call-non-callable]
print("Starting client...")
if client_configfile.config.uri is None or client_configfile.config.username is None or client_configfile.config.password is None:
pytest.skip("Please ensure you have a username, password and uri in the config")
pytest.skip("Please ensure you have a username, password and uri in the config") # type: ignore[call-non-callable]
print(f"Doing client.authenticate for {client_configfile.config.username}")
client_configfile.config.auth_token = None

View file

@ -27,8 +27,7 @@ async def client() -> KanidmClient:
def test_load_config_file() -> None:
"""tests that the file loads"""
if not Path(EXAMPLE_CONFIG_FILE).expanduser().resolve().exists():
print("Can't find client config file", file=sys.stderr)
pytest.skip()
pytest.skip(f"Can't find client config file {EXAMPLE_CONFIG_FILE}") # type: ignore[call-non-callable]
print("Loading config file")
config = load_config(EXAMPLE_CONFIG_FILE)
assert config.get("uri") == "https://localhost:8443"

View file

@ -69,7 +69,7 @@ def test_tokenstuff() -> None:
info = token_store.token_info("idm_admin")
print(f"Parsed token: {info}")
if info is None:
pytest.skip()
pytest.skip("No token!") # type: ignore[call-non-callable]
print(info.expiry_datetime)
assert (
datetime(

View file

@ -16,7 +16,7 @@ async def client() -> KanidmClient:
config_file=Path(__file__).parent.parent.parent / "examples/config_localhost",
)
except FileNotFoundError as error:
raise pytest.skip(f"File not found: {error}")
pytest.skip(f"File not found: {error}") # type: ignore[call-non-callable]
return client
@ -32,7 +32,7 @@ async def test_oauth2_rs_list(client: KanidmClient) -> None:
password = os.getenv("KANIDM_PASSWORD")
if password is None:
print("No KANIDM_PASSWORD env var set for testing")
raise pytest.skip("No KANIDM_PASSWORD env var set for testing")
pytest.skip("No KANIDM_PASSWORD env var set for testing") # type: ignore[call-non-callable]
auth_resp = await client.authenticate_password(username, password, update_internal_auth_token=True)
if auth_resp.state is None:

View file

@ -6,7 +6,7 @@ import logging
import pytest
# pylint: disable=unused-import
from testutils import client, client_configfile
from .testutils import client, client_configfile
from kanidm import KanidmClient
logging.basicConfig(level=logging.DEBUG)
@ -21,7 +21,7 @@ async def test_radius_call(client_configfile: KanidmClient) -> None:
print("Doing auth_init using token")
if client_configfile.config.auth_token is None:
pytest.skip("You can't test auth if you don't have an auth_token in ~/.config/kanidm")
pytest.skip("You can't test auth if you don't have an auth_token in ~/.config/kanidm") # type: ignore[call-non-callable]
result = await client_configfile.get_radius_token(RADIUS_TEST_USER)
print(f"{result=}")

View file

@ -3,7 +3,7 @@
import pytest
import aiohttp.client_exceptions
from testutils import client
from .testutils import client
from kanidm import KanidmClient

View file

@ -187,7 +187,7 @@ async def test_ssl_untrusted_root_configured() -> None:
testcert = Path("./tests/badssl_trusted_ca.pem").resolve()
if not testcert.exists():
pytest.skip(f"The trusted cert is missing from {testcert}")
pytest.skip(f"The trusted cert is missing from {testcert}") # type: ignore[call-non-callable]
client = KanidmClient(
uri="https://untrusted-root.badssl.com/",

View file

@ -2,30 +2,30 @@
from logging import DEBUG, basicConfig, getLogger
from pathlib import Path
from typing import Any
from typing import Any, Optional
import pytest
from kanidm import KanidmClient
@pytest.fixture(scope="function")
async def client() -> KanidmClient:
async def client() -> Optional[KanidmClient]:
"""sets up a client with a basic thing"""
try:
basicConfig(level=DEBUG)
return KanidmClient(uri="https://idm.example.com")
except FileNotFoundError:
raise pytest.skip("Couldn't find config file...")
pytest.skip("Couldn't find config file...") # type: ignore[call-non-callable]
@pytest.fixture(scope="function")
async def client_configfile() -> KanidmClient:
async def client_configfile() -> Optional[KanidmClient]:
"""sets up a client from a config file"""
try:
return KanidmClient(config_file=Path("~/.config/kanidm"))
except FileNotFoundError:
raise pytest.skip("Couldn't find config file...")
pytest.skip("Couldn't find config file...") # type: ignore[call-non-callable]
class MockResponse:

2200
pykanidm/uv.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
3.12

3
rlm_python/README.md Normal file
View file

@ -0,0 +1,3 @@
# rlm_python
Kanidm FreeRADIUS module.

View file

@ -1,2 +1,13 @@
[tool.ruff]
line-length = 150
[project]
name = "rlm-python"
version = "0.1.0"
description = "FreeRADIUS Kanidm module"
readme = "README.md"
requires-python = ">=3.10"
dependencies = ["kanidm"]
[dependency-groups]
dev = ["mypy>=1.15.0", "pytest>=8.3.5", "ruff>=0.11.9", "ty>=0.0.0a8"]
[tool.uv.sources]
kanidm = { path = "../pykanidm" }

View file

@ -1,4 +1,4 @@
""" entrypoint for kanidm's RADIUS module """
"""entrypoint for kanidm's RADIUS module"""
import atexit
import os
@ -7,15 +7,16 @@ import subprocess
import shutil
import signal
import sys
from typing import Any
from typing import Any, Optional
# import toml
import kanidm.radius
from kanidm.radius import CONFIG_PATHS
from kanidm.types import KanidmClientConfig
from kanidm.utils import load_config
DEBUG = True
if os.environ.get('DEBUG', False):
if os.environ.get("DEBUG", False):
DEBUG = True
CERT_SERVER_DEST = "/etc/raddb/certs/server.pem"
@ -23,54 +24,62 @@ CERT_CA_DEST = "/etc/raddb/certs/ca.pem"
CERT_CA_DIR = "/etc/raddb/certs/"
CERT_DH_DEST = "/etc/raddb/certs/dh.pem"
# pylint: disable=unused-argument
def _sigchild_handler(
*args: Any,
**kwargs: Any,
) -> None:
""" handler for SIGCHLD call"""
) -> None:
"""handler for SIGCHLD call"""
print("Received SIGCHLD ...", file=sys.stderr)
os.waitpid(-1, os.WNOHANG)
def write_clients_conf(
kanidm_config_object: KanidmClientConfig,
) -> None:
""" writes out the config file """
) -> None:
"""writes out the config file"""
raddb_config_file = Path("/etc/raddb/clients.conf")
with raddb_config_file.open('w', encoding='utf-8') as file_handle:
with raddb_config_file.open("w", encoding="utf-8") as file_handle:
for client in kanidm_config_object.radius_clients:
file_handle.write(f"client {client.name} {{\n" )
file_handle.write(f"client {client.name} {{\n")
file_handle.write(f" ipaddr = {client.ipaddr}\n")
file_handle.write(f" secret = {client.secret}\n" )
file_handle.write(' proto = *\n')
file_handle.write('}\n')
file_handle.write(f" secret = {client.secret}\n")
file_handle.write(" proto = *\n")
file_handle.write("}\n")
def setup_certs(
kanidm_config_object: KanidmClientConfig,
) -> None:
""" sets up certificates """
) -> None:
"""sets up certificates"""
if kanidm_config_object.radius_ca_path:
cert_ca = Path(kanidm_config_object.radius_ca_path).expanduser().resolve()
if not cert_ca.exists():
print(f"Failed to find radiusd ca file ({cert_ca}), quitting!", file=sys.stderr)
print(
f"Failed to find radiusd ca file ({cert_ca}), quitting!",
file=sys.stderr,
)
sys.exit(1)
if cert_ca != CERT_CA_DEST:
if cert_ca != Path(CERT_CA_DEST):
print(f"Copying {cert_ca} to {CERT_CA_DEST}")
try:
shutil.copyfile(cert_ca, CERT_CA_DEST)
except shutil.SameFileError:
pass
# This dir can also contain crls!
if kanidm_config_object.radius_ca_dir:
cert_ca_dir = Path(kanidm_config_object.radius_ca_dir).expanduser().resolve()
if not cert_ca_dir.exists():
print(f"Failed to find radiusd ca dir ({cert_ca_dir}), quitting!", file=sys.stderr)
print(
f"Failed to find radiusd ca dir ({cert_ca_dir}), quitting!",
file=sys.stderr,
)
sys.exit(1)
if cert_ca_dir != CERT_CA_DIR:
if cert_ca_dir != Path(CERT_CA_DIR):
print(f"Copying {cert_ca_dir} to {CERT_CA_DIR}")
shutil.copytree(cert_ca_dir, CERT_CA_DIR, dirs_exist_ok=True)
@ -83,7 +92,7 @@ def setup_certs(
print(
f"Failed to find server keyfile ({server_key}), quitting!",
file=sys.stderr,
)
)
sys.exit(1)
server_cert = Path(kanidm_config_object.radius_cert_path).expanduser().resolve()
@ -91,18 +100,19 @@ def setup_certs(
print(
f"Failed to find server cert file ({server_cert}), quitting!",
file=sys.stderr,
)
)
sys.exit(1)
# concat key + cert into /etc/raddb/certs/server.pem
with open(CERT_SERVER_DEST, 'w', encoding='utf-8') as file_handle:
with open(CERT_SERVER_DEST, "w", encoding="utf-8") as file_handle:
file_handle.write(server_cert.read_text(encoding="utf-8"))
file_handle.write('\n')
file_handle.write("\n")
file_handle.write(server_key.read_text(encoding="utf-8"))
def kill_radius(
proc: subprocess.Popen,
) -> None:
""" handler to kill the radius server once the script exits """
proc: subprocess.Popen[Any],
) -> None:
"""handler to kill the radius server once the script exits"""
if proc is None:
pass
else:
@ -116,8 +126,9 @@ def kill_radius(
proc.wait()
def find_freeradius_bin() -> str:
""" finds the binary """
def find_freeradius_bin() -> Optional[str]:
"""finds the binary"""
binary_paths = [
"/usr/sbin/radiusd",
"/usr/sbin/freeradius",
@ -129,37 +140,44 @@ def find_freeradius_bin() -> str:
print(f"Failed to find FreeRADIUS binary, looked in {lookedin}")
sys.exit(1)
def run_radiusd() -> None:
""" run the server """
"""run the server"""
if DEBUG:
cmd_args = [ "-X" ]
cmd_args = ["-X"]
else:
cmd_args = [ "-f", "-l", "stdout" ]
with subprocess.Popen(
[find_freeradius_bin()] + cmd_args,
stderr=subprocess.STDOUT,
cmd_args = ["-f", "-l", "stdout"]
freeradius_bin = find_freeradius_bin()
if freeradius_bin is None:
print("Failed to find FreeRADIUS binary, quitting!", file=sys.stderr)
sys.exit(1)
else:
with subprocess.Popen(
[freeradius_bin] + cmd_args,
stderr=subprocess.STDOUT,
) as proc:
# print(proc, file=sys.stderr)
atexit.register(kill_radius, proc)
proc.wait()
# print(proc, file=sys.stderr)
atexit.register(kill_radius, proc)
proc.wait()
if __name__ == '__main__':
if __name__ == "__main__":
signal.signal(signal.SIGCHLD, _sigchild_handler)
config_file = kanidm.radius.find_radius_config_path()
if config_file is None:
print(
"Failed to find configuration file ({config_file}), quitting!",
f"Failed to find configuration file in ({CONFIG_PATHS}), quitting!",
file=sys.stderr,
)
)
sys.exit(1)
kanidm_config = KanidmClientConfig.model_validate(load_config(config_file))
setup_certs(kanidm_config)
write_clients_conf(kanidm_config)
print("Configuration set up, starting...")
try:
run_radiusd()
except KeyboardInterrupt as ki:
print(ki)
else:
kanidm_config = KanidmClientConfig.model_validate(load_config(config_file))
setup_certs(kanidm_config)
write_clients_conf(kanidm_config)
print("Configuration set up, starting...")
try:
run_radiusd()
except KeyboardInterrupt as ki:
print(ki)

1046
rlm_python/uv.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,6 @@
FROM rust:latest
COPY ./scripts/install_ubuntu_dependencies.sh /tmp/
RUN /tmp/install_ubuntu_dependencies.sh
WORKDIR /kanidm

View file

@ -10,9 +10,9 @@ if [ ! -d ".venv" ]; then
# shellcheck disable=SC1091
source .venv/bin/activate
pip install --upgrade pip
pip install poetry pytest ruff mypy black
pip install uv
echo "Installing in virtualenv"
pip install -e pykanidm
pip install -e .
fi
# shellcheck disable=SC1091

View file

@ -45,7 +45,7 @@ fi
# defaults
KANIDM_CONFIG_FILE="./insecure_server.toml"
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
KANIDM_URL="$(grep origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
# wait for them to shut down the server if it's running...
@ -89,7 +89,7 @@ IDM_ADMIN_USER="idm_admin@localhost"
echo "Resetting the idm_admin user..."
IDM_ADMIN_PASS_RAW="$(${KANIDMD} recover-account idm_admin -o json 2>&1)"
IDM_ADMIN_PASS="$(echo "${IDM_ADMIN_PASS_RAW}" | grep password | jq -r .password)"
if [ -z "${IDM_ADMIN_PASS}" ] || [ "${IDM_ADMIN_PASS}" == "null " ]; then
if [ -z "${IDM_ADMIN_PASS}" ] || [ "${IDM_ADMIN_PASS}" == "null" ]; then
echo "Failed to reset idm_admin password!"
echo "Raw output:"
echo "${IDM_ADMIN_PASS_RAW}"

View file

@ -40,7 +40,6 @@ cargo run --bin kanidmd --release server &
KANIDMD_PID=$!
echo "Kanidm PID: ${KANIDMD_PID}"
if [ "$(jobs -p | wc -l)" -eq 0 ]; then
echo "Kanidmd failed to start!"
exit 1
@ -49,12 +48,18 @@ fi
ATTEMPT=0
KANIDM_CONFIG_FILE="./insecure_server.toml"
KANIDM_URL="$(rg origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
if [ -f "${KANIDM_CONFIG_FILE}" ]; then
echo "Found config file ${KANIDM_CONFIG_FILE}"
else
echo "Config file ${KANIDM_CONFIG_FILE} not found!"
exit 1
fi
KANIDM_URL="$(grep origin "${KANIDM_CONFIG_FILE}" | awk '{print $NF}' | tr -d '"')"
KANIDM_CA_PATH="/tmp/kanidm/ca.pem"
while true; do
echo "Waiting for the server to start... testing ${KANIDM_URL}"
curl --cacert "${KANIDM_CA_PATH}" -fs "${KANIDM_URL}/status" >/dev/null && break
echo "Waiting for the server to start... testing url '${KANIDM_URL}'"
curl --cacert "${KANIDM_CA_PATH}" -f "${KANIDM_URL}/status" >/dev/null && break
sleep 2
ATTEMPT="$((ATTEMPT + 1))"
if [ "${ATTEMPT}" -gt 3 ]; then

View file

@ -106,4 +106,4 @@ HEALTHCHECK \
--retries=3 \
CMD [ "/sbin/kanidmd", "healthcheck", "-c", "/data/server.toml"]
CMD [ "/sbin/kanidmd", "server", "-c", "/data/server.toml"]
CMD [ "/sbin/kanidmd", "server"]

View file

@ -27,6 +27,7 @@ axum-htmx = { workspace = true }
axum-extra = { workspace = true }
axum-macros = { workspace = true }
bytes = { workspace = true }
cidr = { workspace = true, features = ["serde"] }
chrono = { workspace = true }
compact_jwt = { workspace = true }
cron = { workspace = true }
@ -60,7 +61,7 @@ tokio-openssl = { workspace = true }
tokio-util = { workspace = true, features = ["codec"] }
toml = { workspace = true }
tower = { version = "0.5.2", features = ["tokio-stream", "tracing"] }
tower-http = { version = "0.6.2", features = [
tower-http = { version = "0.6.4", features = [
"compression-gzip",
"fs",
"tokio",

View file

@ -4,7 +4,7 @@
//! These components should be "per server". Any "per domain" config should be in the system
//! or domain entries that are able to be replicated.
use hashbrown::HashSet;
use cidr::IpCidr;
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
use kanidm_proto::internal::FsType;
use kanidm_proto::messages::ConsoleOutputMode;
@ -110,11 +110,11 @@ pub enum LdapAddressInfo {
#[default]
None,
#[serde(rename = "proxy-v2")]
ProxyV2(HashSet<IpAddr>),
ProxyV2(Vec<IpCidr>),
}
impl LdapAddressInfo {
pub fn trusted_proxy_v2(&self) -> Option<HashSet<IpAddr>> {
pub fn trusted_proxy_v2(&self) -> Option<Vec<IpCidr>> {
if let Self::ProxyV2(trusted) = self {
Some(trusted.clone())
} else {
@ -139,7 +139,7 @@ impl Display for LdapAddressInfo {
}
pub(crate) enum AddressSet {
NonContiguousIpSet(HashSet<IpAddr>),
NonContiguousIpSet(Vec<IpCidr>),
All,
}
@ -147,7 +147,9 @@ impl AddressSet {
pub(crate) fn contains(&self, ip_addr: &IpAddr) -> bool {
match self {
Self::All => true,
Self::NonContiguousIpSet(range) => range.contains(ip_addr),
Self::NonContiguousIpSet(range) => {
range.iter().any(|ip_cidr| ip_cidr.contains(ip_addr))
}
}
}
}
@ -157,13 +159,13 @@ pub enum HttpAddressInfo {
#[default]
None,
#[serde(rename = "x-forward-for")]
XForwardFor(HashSet<IpAddr>),
XForwardFor(Vec<IpCidr>),
// IMPORTANT: This is undocumented, and only exists for backwards compat
// with config v1 which has a boolean toggle for this option.
#[serde(rename = "x-forward-for-all-source-trusted")]
XForwardForAllSourcesTrusted,
#[serde(rename = "proxy-v2")]
ProxyV2(HashSet<IpAddr>),
ProxyV2(Vec<IpCidr>),
}
impl HttpAddressInfo {
@ -175,7 +177,7 @@ impl HttpAddressInfo {
}
}
pub(crate) fn trusted_proxy_v2(&self) -> Option<HashSet<IpAddr>> {
pub(crate) fn trusted_proxy_v2(&self) -> Option<Vec<IpCidr>> {
if let Self::ProxyV2(trusted) = self {
Some(trusted.clone())
} else {
@ -1170,3 +1172,32 @@ impl ConfigurationBuilder {
})
}
}
#[cfg(test)]
mod tests {
use cidr::{IpCidr, Ipv4Cidr, Ipv6Cidr};
use std::net::{Ipv4Addr, Ipv6Addr};
#[test]
fn assert_cidr_parsing_behaviour() {
// Assert that we can parse individual hosts, and ranges
let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.1\"").unwrap();
let expect_ip_cidr = IpCidr::from(Ipv4Addr::new(127, 0, 0, 1));
assert_eq!(parsed_ip_cidr, expect_ip_cidr);
let parsed_ip_cidr: IpCidr = serde_json::from_str("\"127.0.0.0/8\"").unwrap();
let expect_ip_cidr = IpCidr::from(Ipv4Cidr::new(Ipv4Addr::new(127, 0, 0, 0), 8).unwrap());
assert_eq!(parsed_ip_cidr, expect_ip_cidr);
// Same for ipv6
let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::1\"").unwrap();
let expect_ip_cidr = IpCidr::from(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0x0001));
assert_eq!(parsed_ip_cidr, expect_ip_cidr);
let parsed_ip_cidr: IpCidr = serde_json::from_str("\"2001:0db8::/64\"").unwrap();
let expect_ip_cidr = IpCidr::from(
Ipv6Cidr::new(Ipv6Addr::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 0), 64).unwrap(),
);
assert_eq!(parsed_ip_cidr, expect_ip_cidr);
}
}

View file

@ -211,7 +211,6 @@ impl Modify for SecurityAddon {
schemas(
attribute::Attribute,
scim_v1::ScimSyncState,
scim_v1::ScimSyncRequest,
scim_v1::ScimSyncRetentionMode,

View file

@ -29,10 +29,10 @@ use axum::{
Router,
};
use axum_extra::extract::cookie::CookieJar;
use cidr::IpCidr;
use compact_jwt::{error::JwtError, JwsCompact, JwsHs256Signer, JwsVerifier};
use futures::pin_mut;
use haproxy_protocol::{ProxyHdrV2, RemoteAddress};
use hashbrown::HashSet;
use hyper::body::Incoming;
use hyper_util::rt::{TokioExecutor, TokioIo};
use kanidm_lib_crypto::x509_cert::{der::Decode, x509_public_key_s256, Certificate};
@ -43,7 +43,6 @@ use serde::de::DeserializeOwned;
use sketching::*;
use std::fmt::Write;
use std::io::ErrorKind;
use std::net::IpAddr;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Arc;
@ -363,7 +362,7 @@ async fn server_tls_loop(
mut rx: broadcast::Receiver<CoreAction>,
server_message_tx: broadcast::Sender<CoreAction>,
mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) {
pin_mut!(listener);
@ -404,7 +403,7 @@ async fn server_plaintext_loop(
listener: TcpListener,
app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>,
mut rx: broadcast::Receiver<CoreAction>,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) {
pin_mut!(listener);
@ -438,7 +437,7 @@ pub(crate) async fn handle_conn(
stream: TcpStream,
app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>,
connection_addr: SocketAddr,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) -> Result<(), std::io::Error> {
let (stream, client_addr) =
process_client_addr(stream, connection_addr, trusted_proxy_v2_ips).await?;
@ -462,7 +461,7 @@ pub(crate) async fn handle_tls_conn(
stream: TcpStream,
app: IntoMakeServiceWithConnectInfo<Router, ClientConnInfo>,
connection_addr: SocketAddr,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) -> Result<(), std::io::Error> {
let (stream, client_addr) =
process_client_addr(stream, connection_addr, trusted_proxy_v2_ips).await?;
@ -531,10 +530,14 @@ pub(crate) async fn handle_tls_conn(
async fn process_client_addr(
stream: TcpStream,
connection_addr: SocketAddr,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) -> Result<(TcpStream, SocketAddr), std::io::Error> {
let enable_proxy_v2_hdr = trusted_proxy_v2_ips
.map(|trusted| trusted.contains(&connection_addr.ip()))
.map(|trusted| {
trusted
.iter()
.any(|ip_cidr| ip_cidr.contains(&connection_addr.ip()))
})
.unwrap_or_default();
let (stream, client_addr) = if enable_proxy_v2_hdr {

View file

@ -119,9 +119,10 @@ struct SetUnixCredPartial {
#[derive(Template)]
#[template(path = "credential_update_add_ssh_publickey_partial.html")]
struct AddSshPublicKeyPartial {
key_title: Option<String>,
title_error: Option<String>,
key_error: Option<String>,
key_value: Option<String>,
key_error: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
@ -901,9 +902,10 @@ pub(crate) async fn view_add_ssh_publickey(
let new_key = match opt_form {
None => {
return Ok((AddSshPublicKeyPartial {
key_title: None,
title_error: None,
key_error: None,
key_value: None,
key_error: None,
},)
.into_response());
}
@ -920,9 +922,10 @@ pub(crate) async fn view_add_ssh_publickey(
let publickey = match SshPublicKey::from_string(&new_key.key) {
Err(_) => {
return Ok((AddSshPublicKeyPartial {
key_title: Some(new_key.title),
title_error: None,
key_error: Some("Key cannot be parsed".to_string()),
key_value: Some(new_key.key),
key_error: Some("Key cannot be parsed".to_string()),
},)
.into_response());
}
@ -932,7 +935,7 @@ pub(crate) async fn view_add_ssh_publickey(
.qe_r_ref
.handle_idmcredentialupdate(
cu_session_token,
CURequest::SshPublicKey(new_key.title, publickey),
CURequest::SshPublicKey(new_key.title.clone(), publickey),
kopid.eventid,
)
.await;
@ -966,6 +969,7 @@ pub(crate) async fn view_add_ssh_publickey(
status,
HxPushUrl(Uri::from_static("/ui/reset/add_ssh_publickey")),
AddSshPublicKeyPartial {
key_title: Some(new_key.title),
title_error,
key_error,
key_value: Some(new_key.key),

View file

@ -1,15 +1,15 @@
use crate::actors::QueryServerReadV1;
use crate::CoreAction;
use cidr::IpCidr;
use futures_util::sink::SinkExt;
use futures_util::stream::StreamExt;
use haproxy_protocol::{ProxyHdrV2, RemoteAddress};
use hashbrown::HashSet;
use kanidmd_lib::idm::ldap::{LdapBoundToken, LdapResponseState};
use kanidmd_lib::prelude::*;
use ldap3_proto::proto::LdapMsg;
use ldap3_proto::LdapCodec;
use openssl::ssl::{Ssl, SslAcceptor};
use std::net::{IpAddr, SocketAddr};
use std::net::SocketAddr;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
@ -122,10 +122,14 @@ async fn client_tls_accept(
tls_acceptor: SslAcceptor,
connection_addr: SocketAddr,
qe_r_ref: &'static QueryServerReadV1,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) {
let enable_proxy_v2_hdr = trusted_proxy_v2_ips
.map(|trusted| trusted.contains(&connection_addr.ip()))
.map(|trusted| {
trusted
.iter()
.any(|ip_cidr| ip_cidr.contains(&connection_addr.ip()))
})
.unwrap_or_default();
let (stream, client_addr) = if enable_proxy_v2_hdr {
@ -186,7 +190,7 @@ async fn ldap_tls_acceptor(
qe_r_ref: &'static QueryServerReadV1,
mut rx: broadcast::Receiver<CoreAction>,
mut tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
trusted_proxy_v2_ips: Option<Arc<HashSet<IpAddr>>>,
trusted_proxy_v2_ips: Option<Arc<Vec<IpCidr>>>,
) {
loop {
tokio::select! {
@ -249,7 +253,7 @@ pub(crate) async fn create_ldap_server(
qe_r_ref: &'static QueryServerReadV1,
rx: broadcast::Receiver<CoreAction>,
tls_acceptor_reload_rx: mpsc::Receiver<SslAcceptor>,
trusted_proxy_v2_ips: Option<HashSet<IpAddr>>,
trusted_proxy_v2_ips: Option<Vec<IpCidr>>,
) -> Result<tokio::task::JoinHandle<()>, ()> {
if address.starts_with(":::") {
// takes :::xxxx to xxxx

View file

@ -92,7 +92,7 @@ pub(crate) async fn create_repl_server(
Ok((repl_handle, ctrl_tx))
}
#[instrument(level = "info", skip_all)]
#[instrument(level = "debug", skip_all)]
/// This returns the remote address that worked, so you can try that first next time
async fn repl_consumer_connect_supplier(
domain: &str,
@ -116,7 +116,10 @@ async fn repl_consumer_connect_supplier(
)
.await
{
Ok(Ok(tc)) => tc,
Ok(Ok(tc)) => {
trace!("Connection established to peer on {:?}", sock_addr);
tc
}
Ok(Err(err)) => {
debug!(?err, "Failed to connect to {}", sock_addr);
continue;
@ -127,8 +130,6 @@ async fn repl_consumer_connect_supplier(
}
};
trace!("Connection established to peer on {:?}", sock_addr);
let mut tlsstream = match Ssl::new(tls_connector.context())
.and_then(|tls_obj| SslStream::new(tls_obj, tcpstream))
{
@ -236,7 +237,7 @@ async fn repl_run_consumer_refresh(
Ok(Some(addr))
}
#[instrument(level="info", skip(tls_connector, idms), fields(eventid=Uuid::new_v4().to_string()))]
#[instrument(level="debug", skip(tls_connector, idms), fields(eventid=Uuid::new_v4().to_string()))]
async fn repl_run_consumer(
domain: &str,
sock_addrs: &[SocketAddr],
@ -282,11 +283,11 @@ async fn repl_run_consumer(
changes
}
Ok(SupplierResponse::Pong) | Ok(SupplierResponse::Refresh(_)) => {
error!("Supplier Response contains invalid State");
error!("Supplier Response contains invalid state");
return None;
}
Err(err) => {
error!(?err, "consumer decode error, unable to continue.");
error!(?err, "Consumer decode error, unable to continue.");
return None;
}
}
@ -306,7 +307,7 @@ async fn repl_run_consumer(
}) {
Ok(state) => state,
Err(err) => {
error!(?err, "consumer was not able to apply changes.");
error!(?err, "Consumer was not able to apply changes.");
return None;
}
}
@ -365,7 +366,7 @@ async fn repl_run_consumer(
return None;
}
warn!("Replication refresh was successful.");
info!("Replication refresh was successful.");
Some(socket_addr)
}
@ -544,7 +545,7 @@ async fn repl_task(
info!("Replica task for {} has stopped.", origin);
}
#[instrument(level = "info", skip_all)]
#[instrument(level = "debug", skip_all)]
async fn handle_repl_conn(
max_frame_bytes: usize,
tcpstream: TcpStream,
@ -626,6 +627,7 @@ async fn handle_repl_conn(
debug!(?client_address, "replication client disconnected 🛬");
}
/// This is the main acceptor for the replication server.
async fn repl_acceptor(
listener: TcpListener,
idms: Arc<IdmServer>,

View file

@ -6,26 +6,38 @@
hx-post="/ui/reset/add_ssh_publickey">
<div>
<label for="key-title" class="form-label">Title</label>
<input type="text" class="form-control(% if let Some(_) = title_error %) is-invalid(% endif %)" id="key-title" name="title" aria-describedby="title-validation-feedback">
<input type="text"
class="form-control(% if let Some(_) = title_error %) is-invalid(% endif %)"
id="key-title" name="title"
aria-describedby="title-validation-feedback"
autocapitalize="off" autocomplete="off" required
value="(% if let Some(key_title) = key_title %)(( key_title ))(% endif %)">
(% if let Some(title_error) = title_error %)
<div id="title-validation-feedback" class="invalid-feedback">
(% if let Some(title_error) = title_error %)(( title_error ))(% endif %)
(( title_error ))
</div>
(% endif %)
</div>
<div>
<label for="key-content" class="form-label">Key</label>
<textarea class="form-control(% if let Some(_) = key_error %) is-invalid(% endif %)" id="key-content" rows="5" name="key"
<textarea
class="form-control(% if let Some(_) = key_error %) is-invalid(% endif %)"
id="key-content" rows="5" name="key"
aria-describedby="key-validation-feedback"
placeholder="Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'"
>(% if let Some(key_value) = key_value %)(( key_value ))(% endif %)</textarea>
autocapitalize="off" autocomplete="off" required
placeholder="Begins with 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519', 'sk-ecdsa-sha2-nistp256@openssh.com', or 'sk-ssh-ed25519@openssh.com'">(% if let Some(key_value) = key_value %)(( key_value ))(% endif %)</textarea>
(% if let Some(key_error) = key_error %)
<div id="key-validation-feedback" class="invalid-feedback">
(% if let Some(key_error) = key_error %)(( key_error ))(% endif %)
(( key_error ))
</div>
</div>
(% endif%)
</div>
<div class="column-gap-2 d-flex justify-content-end mt-2" hx-target="#credentialUpdateDynamicSection">
<button type="button" class="btn btn-danger" hx-get=((Urls::CredReset)) hx-target="body">Cancel</button>
<div class="column-gap-2 d-flex justify-content-end mt-2"
hx-target="#credentialUpdateDynamicSection">
<button type="button" class="btn btn-danger"
hx-get=((Urls::CredReset)) hx-target="body">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>

View file

@ -72,7 +72,7 @@ priority = "optional"
changelog = "../../target/debian/changelog" # Generated by platform/debian/build_debs.sh
assets = [
[ "target/release/kanidmd", "usr/bin/", "755" ],
[ "debian/group.conf", "usr/lib/sysusers.d/kandimd.conf", "644" ],
[ "debian/group.conf", "usr/lib/sysusers.d/kanidmd.conf", "644" ],
[ "debian/server.toml", "etc/kanidmd/server.toml", "640" ],
[ "../../examples/server.toml", "usr/share/kanidmd/", "444" ],
[ "../core/static/**/*", "usr/share/kanidmd/static", "444" ],

View file

@ -343,7 +343,7 @@ impl QueryServerWriteTransaction<'_> {
}
}
#[instrument(level = "info", skip_all)]
#[instrument(level = "debug", skip_all)]
fn consumer_apply_changes_v1(
&mut self,
ctx_domain_version: DomainVersion,
@ -394,7 +394,7 @@ impl QueryServerWriteTransaction<'_> {
})?;
// == ⚠️ Below this point we begin to make changes! ==
info!(
debug!(
"Proceeding to apply incremental from domain {:?} at level {}",
ctx_domain_uuid, ctx_domain_version
);

View file

@ -148,14 +148,14 @@ impl QueryServerReadTransaction<'_> {
RangeDiffStatus::Ok(ranges) => ranges,
RangeDiffStatus::Refresh { lag_range } => {
error!("Replication - Consumer is lagging and must be refreshed.");
info!(?lag_range);
debug!(?lag_range);
debug!(consumer_ranges = ?ctx_ranges);
debug!(supplier_ranges = ?our_ranges);
return Ok(ReplIncrementalContext::RefreshRequired);
}
RangeDiffStatus::Unwilling { adv_range } => {
error!("Replication - Supplier is lagging and must be investigated.");
info!(?adv_range);
debug!(?adv_range);
debug!(consumer_ranges = ?ctx_ranges);
debug!(supplier_ranges = ?our_ranges);
return Ok(ReplIncrementalContext::UnwillingToSupply);
@ -164,9 +164,7 @@ impl QueryServerReadTransaction<'_> {
lag_range,
adv_range,
} => {
error!("Replication Critical - Consumers are advanced of us, and also lagging! This must be immediately investigated!");
info!(?lag_range);
info!(?adv_range);
error!(?adv_range, ?lag_range, "Replication Critical - Consumers are advanced of us, and also lagging! This must be immediately investigated!");
debug!(consumer_ranges = ?ctx_ranges);
debug!(supplier_ranges = ?our_ranges);
return Ok(ReplIncrementalContext::UnwillingToSupply);

View file

@ -252,4 +252,63 @@ mod tests {
// Test that we can parse json values into a valueset.
crate::valueset::scim_json_put_reflexive::<ValueSetSshKey>(&vs, &[])
}
#[test]
/// this is a test case for bad characters in SSH keys
fn test_invalid_character() {
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEÀAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
// ^ note the À here
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
"zPrVSdrYEDldxH9+a86dBZhdm0è15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
"methyst");
println!("bytes of À {:?}", "À".as_bytes());
let found_index = ecdsa.find("À").expect("Failed to find è in string");
assert_eq!(found_index, 51, "Expected index 51");
let bad_ssh_error = SshPublicKey::from_string(ecdsa);
assert!(
bad_ssh_error.is_err(),
"Expected error, but got: {:?}",
bad_ssh_error
);
if let Err(err) = bad_ssh_error {
assert_eq!(
err.to_string(),
format!("Invalid symbol 195, offset {}.", found_index - 20)
); // the offset is 31 because the string has a 20 character leading key type plus the space
}
}
#[test]
/// this is a test case for bad characters in SSH keys
fn test_assert_comments() {
// Comment is okay.
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
"zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@a",
"methyst");
let _ = SshPublicKey::from_string(ecdsa).unwrap();
// No comment is okay.
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
"zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag==");
let _ = SshPublicKey::from_string(ecdsa).unwrap();
// No comment is okay (spaces to end of line)
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
"zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== ");
let _ = SshPublicKey::from_string(ecdsa).unwrap();
// Comment may have spaces
let ecdsa = concat!("ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3B",
"tOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81l",
"zPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== I'm a giraffe! ");
let _ = SshPublicKey::from_string(ecdsa).unwrap();
}
}

View file

@ -48,6 +48,7 @@ url = { workspace = true, features = ["serde"] }
kanidm_build_profiles = { workspace = true }
[dev-dependencies]
cidr = { workspace = true }
compact_jwt = { workspace = true }
escargot = "0.5.13"
# used for webdriver testing

View file

@ -1,3 +1,4 @@
use cidr::IpCidr;
use kanidm_client::KanidmClient;
use kanidm_proto::constants::X_FORWARDED_FOR;
use kanidmd_core::config::HttpAddressInfo;
@ -51,7 +52,7 @@ async fn dont_trust_xff_send_header(rsclient: &KanidmClient) {
// =====================================================
// *test where we do trust the x-forwarded-for header
#[kanidmd_testkit::test(http_client_address_info = HttpAddressInfo::XForwardFor ( [DEFAULT_IP_ADDRESS].into() ))]
#[kanidmd_testkit::test(http_client_address_info = HttpAddressInfo::XForwardFor ( [IpCidr::from(DEFAULT_IP_ADDRESS)].into() ))]
async fn trust_xff_address_set(rsclient: &KanidmClient) {
inner_test_trust_xff(rsclient).await;
}
@ -284,7 +285,7 @@ async fn proxy_v2_make_request(
Ok(ip_res)
}
#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [DEFAULT_IP_ADDRESS].into() ))]
#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [IpCidr::from(DEFAULT_IP_ADDRESS)].into() ))]
async fn trust_proxy_v2_address_set(test_env: &AsyncTestEnvironment) {
// Send with no header - with proxy v2, a header is ALWAYS required
let proxy_hdr: [u8; 0] = [];
@ -308,7 +309,7 @@ async fn trust_proxy_v2_address_set(test_env: &AsyncTestEnvironment) {
assert_eq!(res, IpAddr::V4(Ipv4Addr::new(172, 24, 12, 118)));
}
#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [ IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)) ].into() ))]
#[kanidmd_testkit::test(with_test_env = true, http_client_address_info = HttpAddressInfo::ProxyV2 ( [ IpCidr::from(Ipv4Addr::new(10, 0, 0, 1)) ].into() ))]
async fn trust_proxy_v2_untrusted(test_env: &AsyncTestEnvironment) {
// Send with a valid header, but we aren't a trusted source.
let proxy_hdr =

View file

@ -24,7 +24,7 @@ libc = { workspace = true }
lazy_static = { workspace = true }
[target."cfg(target_os = \"freebsd\")".build-dependencies]
cc = "^1.2.10"
cc = "^1.2.22"
## Debian packaging
[package.metadata.deb]