Compare commits
201 commits
v1.5.0-dev
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d0cfc69a09 | ||
|
b113262357 | ||
|
d025e8fff0 | ||
|
aee9ed05f3 | ||
|
5458b13398 | ||
|
94b6287e27 | ||
|
b6813a11d3 | ||
|
d79188559f | ||
|
ad012cd6fd | ||
|
82a883089f | ||
|
a2eae53328 | ||
|
ec3db91da0 | ||
|
efaef70abe | ||
|
5b48f1dfe3 | ||
|
567fe7b259 | ||
|
5edc6be51c | ||
|
c75c97893e | ||
|
638904f12c | ||
|
e1b9063b99 | ||
|
bf1e9b0989 | ||
|
11c7266ff3 | ||
|
ef638a62e9 | ||
|
f86bc03a93 | ||
|
46eda59cff | ||
|
b13951a79b | ||
|
1e91f244a2 | ||
|
23bb656c6b | ||
|
b88b6923eb | ||
|
e3243ce6b0 | ||
|
4b4e690642 | ||
|
d6549077fb | ||
|
2c5ce227ae | ||
|
919e0ba6fe | ||
|
23d35dc324 | ||
|
7d9661ef45 | ||
|
dcd5cd23f4 | ||
|
7b2bd38ab2 | ||
|
775dd520cb | ||
|
63deda350c | ||
|
7eedb0159f | ||
|
e98d60a962 | ||
|
25c1c1573e | ||
|
145ffed7c6 | ||
|
b669f38d23 | ||
|
537d6fd93b | ||
|
b6ffb31e4a | ||
|
0e0e8ff844 | ||
|
266dc77536 | ||
|
3edee485dd | ||
|
38c260214b | ||
|
857dcf5087 | ||
|
9611a7f976 | ||
|
f40679cd52 | ||
|
52824b58f1 | ||
|
848af4cecd | ||
|
de506a5f53 | ||
|
7f3b1f2580 | ||
|
9bf17c4846 | ||
|
ed88b72080 | ||
|
d0b0b163fd | ||
|
ce410f440c | ||
|
77271c1720 | ||
|
e838da9a08 | ||
|
94b7285cbb | ||
|
af6f55b1fe | ||
|
211e7d4e89 | ||
|
ccde675cd2 | ||
|
b96fe49b99 | ||
|
c89f0c011e | ||
|
b15ff89b39 | ||
|
1f5ce2617d | ||
|
f68906bf1b | ||
|
7a9bb9eac2 | ||
|
0ce1bbeddc | ||
|
ad3cf8828f | ||
|
43b7f80535 | ||
|
41b2eac1f4 | ||
|
4938c6796b | ||
|
9c2825b9dc | ||
|
9505b5a732 | ||
|
3b3c029e30 | ||
|
99e37e987a | ||
|
d4c5a6f4a9 | ||
|
d3457ff3c1 | ||
|
f93d07b6cc | ||
|
351fdcdef0 | ||
|
1453ba5d74 | ||
|
ed76bdbfb1 | ||
|
12532ee32d | ||
|
10f03e19c0 | ||
|
c324fa92f5 | ||
|
b3be758b74 | ||
|
b03f842728 | ||
|
27cc31dace | ||
|
dd1d148543 | ||
|
419c4a1827 | ||
|
2d439c508f | ||
|
ba24ffb1e0 | ||
|
e7d91ed55d | ||
|
c4bc1ff546 | ||
|
1a29aa7301 | ||
|
063366cba4 | ||
|
16591007dd | ||
|
1983ce19e9 | ||
|
ccf6792104 | ||
|
028bd93059 | ||
|
8ef7d6cb4a | ||
|
a3358828a8 | ||
|
761dda688e | ||
|
b74883ae0d | ||
|
3430a1c31d | ||
|
5562625d75 | ||
|
226274da23 | ||
|
5eb9a4430f | ||
|
227853f8cd | ||
|
c0e733629f | ||
|
009200375f | ||
|
4113c291ed | ||
|
c4441c1fca | ||
|
bbefb0b1b1 | ||
|
b6f63f3605 | ||
|
9f499f3913 | ||
|
c6432cad83 | ||
|
2174b9b251 | ||
|
4f2eb8b5f8 | ||
|
c59f560e50 | ||
|
11438a9dd5 | ||
|
50a7d9d700 | ||
|
1fbbf323fa | ||
|
44e7348f3b | ||
|
0b2f349aec | ||
|
eba8dff23a | ||
|
7e9c33ab03 | ||
|
6c3b8500a2 | ||
|
5d75c9b247 | ||
|
6db0cdc345 | ||
|
6abdb12e35 | ||
|
d62c17cd0e | ||
|
a8d149db16 | ||
|
5dfba2a0ef | ||
|
60cc830ebd | ||
|
4ee9a3a098 | ||
|
b2906829e2 | ||
|
07b9ca8939 | ||
|
cae780e091 | ||
|
5dfbf7ca79 | ||
|
97a1c39d62 | ||
|
9b3350f753 | ||
|
516874460b | ||
|
388ed679a8 | ||
|
64fcb61d5e | ||
|
42459f56b0 | ||
|
4f53bce3d3 | ||
|
ac3cf1f363 | ||
|
b4ca356b8d | ||
|
a9c354d578 | ||
|
ea0e63cc2a | ||
|
8bbdf6bd6a | ||
|
db101e6d26 | ||
|
c1ed939c28 | ||
|
4528a1bda0 | ||
|
52987ab8b2 | ||
|
974fec1d93 | ||
|
a5adf8bcad | ||
|
fa77076a95 | ||
|
615f07f7d3 | ||
|
0054fe17fe | ||
|
767c5014d5 | ||
|
bd53c88229 | ||
|
d2f5e13c97 | ||
|
5393891ea8 | ||
|
809cacdb85 | ||
|
ce0ad8f854 | ||
|
7348c0348a | ||
|
505e7d9530 | ||
|
ed38e92448 | ||
|
155225c0f9 | ||
|
77e13b817a | ||
|
9c49c831c1 | ||
|
dfbcfa865f | ||
|
1218abd8c6 | ||
|
ab4291e8d2 | ||
|
9973385381 | ||
|
0f3f604ba0 | ||
|
4f87ee2fc8 | ||
|
a7394e8875 | ||
|
b3c6eea4e9 | ||
|
853f787327 | ||
|
5572497909 | ||
|
fbbe10e295 | ||
|
c96e9772c7 | ||
|
ea1fcf59e5 | ||
|
1b58e4169a | ||
|
cc7530aa65 | ||
|
d174cbfd72 | ||
|
fbb3b6b2db | ||
|
ce31abeeb0 | ||
|
53dcb5265a | ||
|
d2c329f330 | ||
|
44ddfb98f1 | ||
|
f7d701c344 |
400 changed files with 30419 additions and 13898 deletions
11
.cargo/config.toml
Normal file
11
.cargo/config.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
# The default ld from glibc is impossibly slow and consumes huge amounts of
|
||||
# memory. We use lld by default which often is twice as fast for significantly
|
||||
# less ram consumption.
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld", "-Ctarget-cpu=native"]
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=lld", "-Ctarget-cpu=native"]
|
|
@ -2,7 +2,8 @@
|
|||
// README at: https://github.com/devcontainers/templates/tree/main/src/rust
|
||||
{
|
||||
"name": "Rust",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
|
||||
// https://github.com/devcontainers/images/tree/main/src/rust
|
||||
"image": "mcr.microsoft.com/devcontainers/rust:1-1-bookworm",
|
||||
"features": {
|
||||
},
|
||||
|
@ -13,6 +14,11 @@
|
|||
"source": "devcontainer-cargo-cache-${devcontainerId}",
|
||||
"target": "/usr/local/cargo",
|
||||
"type": "volume"
|
||||
},
|
||||
{
|
||||
"source": "devcontainer-sccache-cache-${devcontainerId}",
|
||||
"target": "/home/vscode/.cache/sccache",
|
||||
"type": "volume"
|
||||
}
|
||||
],
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
|
@ -21,7 +27,17 @@
|
|||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [8443],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "rustup update && rustup default stable && rustup component add rustfmt clippy && sudo apt-get update && sudo apt-get install -y sccache ripgrep libssl-dev pkg-config jq libpam0g-dev libudev-dev cmake build-essential && cargo install cargo-audit mdbook-mermaid mdbook && cargo install mdbook-alerts --version 0.6.4 && cargo install deno --locked"
|
||||
"postCreateCommand": "./scripts/devcontainer_postcreate.sh",
|
||||
|
||||
// postStartCommand runs things once the container's up, in this case, we're starting sccache
|
||||
"postStartCommand": "./scripts/devcontainer_poststart.sh",
|
||||
|
||||
// environment variables
|
||||
"containerEnv": {
|
||||
// "RUSTC_WRAPPER": "sccache",
|
||||
// "CC":"/usr/bin/sccache /usr/bin/clang",
|
||||
"SCCACHE_SERVER_UDS" : "/tmp/sccache.sock"
|
||||
}
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
|
|
@ -10,3 +10,4 @@ kanidmd/sampledata
|
|||
Makefile
|
||||
target
|
||||
test.db
|
||||
Dockerfile
|
||||
|
|
|
@ -2,9 +2,21 @@
|
|||
|
||||
root = true
|
||||
|
||||
|
||||
|
||||
[*.md]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.js]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
||||
|
||||
[*.mjs]
|
||||
tab_width = 4
|
||||
max_line_length = 120
|
||||
print_width = 120
|
16
.github/workflows/clippy.yml
vendored
16
.github/workflows/clippy.yml
vendored
|
@ -19,19 +19,19 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libselinux1-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
libtss2-dev \
|
||||
libudev-dev \
|
||||
pkg-config \
|
||||
tpm-udev \
|
||||
libtss2-dev
|
||||
tpm-udev
|
||||
- name: "Run clippy"
|
||||
run: cargo clippy --lib --bins --examples --all-features
|
||||
fmt:
|
||||
|
@ -39,8 +39,6 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: "Run cargo fmt"
|
||||
run: cargo fmt --check
|
||||
|
|
20
.github/workflows/docker_build_kanidm.yml
vendored
20
.github/workflows/docker_build_kanidm.yml
vendored
|
@ -35,9 +35,15 @@ jobs:
|
|||
needs:
|
||||
- set_tag_values
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
- name: Build kanidm
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
@ -47,6 +53,9 @@ jobs:
|
|||
build-args: |
|
||||
"KANIDM_FEATURES="
|
||||
file: tools/Dockerfile
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
# Must use OCI exporter for multi-arch: https://github.com/docker/buildx/pull/1813
|
||||
outputs: type=oci,dest=/tmp/kanidm-docker.tar
|
||||
- name: Upload artifact
|
||||
|
@ -60,8 +69,8 @@ jobs:
|
|||
# This step is split so that we don't apply "packages: write" permission
|
||||
# except when uploading the final Docker image to GHCR.
|
||||
runs-on: ubuntu-latest
|
||||
if: ( github.ref_type == 'tag' || github.ref == 'refs/heads/master' ) && github.repository == 'kanidm/kanidm'
|
||||
needs: kanidm_build
|
||||
if: ( github.ref_type == 'tag' || github.ref == 'refs/heads/master' )
|
||||
needs: [kanidm_build, set_tag_values]
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
|
@ -71,10 +80,11 @@ jobs:
|
|||
with:
|
||||
name: kanidm-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
- name: Push image to GHCR
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
||||
oras login -u "${{ github.actor }}" --password-stdin ghcr.io
|
||||
oras copy --from-oci-layout "/tmp/kanidm-docker.tar:devel" \
|
||||
"ghcr.io/${{ github.repository_owner }}/kanidm:devel"
|
||||
"ghcr.io/${{ needs.set_tag_values.outputs.owner_lc }}/kanidm:devel"
|
32
.github/workflows/docker_build_kanidmd.yml
vendored
32
.github/workflows/docker_build_kanidmd.yml
vendored
|
@ -35,27 +35,15 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: set_tag_values
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
# images: |
|
||||
# kanidm/kanidmd
|
||||
# ghcr.io/username/app
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=schedule
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
|
||||
- name: Build kanidmd
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
|
@ -64,6 +52,9 @@ jobs:
|
|||
# build-args: |
|
||||
# "KANIDM_BUILD_OPTIONS=-j1"
|
||||
file: server/Dockerfile
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
# Must use OCI exporter for multi-arch: https://github.com/docker/buildx/pull/1813
|
||||
outputs: type=oci,dest=/tmp/kanidmd-docker.tar
|
||||
- name: Upload artifact
|
||||
|
@ -77,8 +68,8 @@ jobs:
|
|||
# This step is split so that we don't apply "packages: write" permission
|
||||
# except when uploading the final Docker image to GHCR.
|
||||
runs-on: ubuntu-latest
|
||||
if: ( github.ref_type== 'tag' || github.ref == 'refs/heads/master' ) && github.repository == 'kanidm/kanidm'
|
||||
needs: kanidmd_build
|
||||
if: ( github.ref_type== 'tag' || github.ref == 'refs/heads/master' )
|
||||
needs: [kanidmd_build, set_tag_values]
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
|
@ -88,10 +79,11 @@ jobs:
|
|||
with:
|
||||
name: kanidmd-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
- name: Push image to GHCR
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
||||
oras login -u "${{ github.actor }}" --password-stdin ghcr.io
|
||||
oras copy --from-oci-layout "/tmp/kanidmd-docker.tar:devel" \
|
||||
"ghcr.io/${{ github.repository_owner }}/kanidmd:devel"
|
||||
"ghcr.io/${{ needs.set_tag_values.outputs.owner_lc }}/kanidmd:devel"
|
20
.github/workflows/docker_build_radiusd.yml
vendored
20
.github/workflows/docker_build_radiusd.yml
vendored
|
@ -35,17 +35,26 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
needs: set_tag_values
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
- name: Build radius
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/arm64,linux/amd64
|
||||
tags: ghcr.io/${{ needs.set_tag_values.outputs.owner_lc }}/radius:devel,ghcr.io/${{ needs.set_tag_values.outputs.owner_lc }}/radius:${{ needs.set_tag_values.outputs.ref_name}}
|
||||
file: rlm_python/Dockerfile
|
||||
context: .
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.annotations }}
|
||||
# Must use OCI exporter for multi-arch: https://github.com/docker/buildx/pull/1813
|
||||
outputs: type=oci,dest=/tmp/radius-docker.tar
|
||||
- name: Upload artifact
|
||||
|
@ -59,8 +68,8 @@ jobs:
|
|||
# This step is split so that we don't apply "packages: write" permission
|
||||
# except when uploading the final Docker image to GHCR.
|
||||
runs-on: ubuntu-latest
|
||||
if: ( github.ref_type == 'tag' || github.ref == 'refs/heads/master' ) && github.repository == 'kanidm/kanidm'
|
||||
needs: radius_build
|
||||
if: ( github.ref_type == 'tag' || github.ref == 'refs/heads/master' )
|
||||
needs: [radius_build, set_tag_values]
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
|
@ -70,7 +79,8 @@ jobs:
|
|||
with:
|
||||
name: radius-docker
|
||||
path: /tmp
|
||||
|
||||
- name: Set up ORAS
|
||||
uses: oras-project/setup-oras@v1
|
||||
# Docker won't directly import OCI images and keep their multi-arch
|
||||
# features, but ORAS will: https://oras.land/docs/commands/oras_copy
|
||||
- name: Push image to GHCR
|
||||
|
@ -78,4 +88,4 @@ jobs:
|
|||
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
||||
oras login -u "${{ github.actor }}" --password-stdin ghcr.io
|
||||
oras copy --from-oci-layout "/tmp/radius-docker.tar:devel" \
|
||||
"ghcr.io/${{ github.repository_owner }}/radius:devel"
|
||||
"ghcr.io/${{ needs.set_tag_values.outputs.owner_lc }}/radius:devel"
|
21
.github/workflows/javascript_lint.yml
vendored
Normal file
21
.github/workflows/javascript_lint.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
name: Javascript Linting
|
||||
"on":
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
javascript_lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run ESLint to check Javascript files
|
||||
run:
|
||||
make eslint
|
||||
javascript_fmt:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Prettier to check Javascript files
|
||||
run:
|
||||
make prettier
|
||||
|
4
.github/workflows/kanidm_individual_book.yml
vendored
4
.github/workflows/kanidm_individual_book.yml
vendored
|
@ -24,9 +24,7 @@ jobs:
|
|||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
|
|
26
.github/workflows/rust_build.yml
vendored
26
.github/workflows/rust_build.yml
vendored
|
@ -27,20 +27,18 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev
|
||||
libssl-dev \
|
||||
libsystemd-dev
|
||||
|
||||
- name: "Build the workspace"
|
||||
run: cargo build --workspace
|
||||
run: cargo build --locked --workspace
|
||||
- name: "Check disk space and size of target, then clean it"
|
||||
run: |
|
||||
df -h
|
||||
|
@ -74,20 +72,18 @@ jobs:
|
|||
with:
|
||||
toolchain: ${{ matrix.rust_version }}
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
sudo apt-get install -y \
|
||||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev
|
||||
libssl-dev \
|
||||
libsystemd-dev
|
||||
|
||||
- name: "Build the workspace"
|
||||
run: cargo build --workspace
|
||||
run: cargo build --locked --workspace
|
||||
- name: "Check disk space and size of target, then clean it"
|
||||
run: |
|
||||
df -h
|
||||
|
@ -116,10 +112,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update && \
|
||||
|
@ -127,6 +120,7 @@ jobs:
|
|||
libpam0g-dev \
|
||||
libudev-dev \
|
||||
libssl-dev \
|
||||
libsystemd-dev \
|
||||
ripgrep
|
||||
- name: "Run the release build test script"
|
||||
env:
|
||||
|
|
6
.github/workflows/windows_build.yml
vendored
6
.github/workflows/windows_build.yml
vendored
|
@ -28,9 +28,7 @@ jobs:
|
|||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.6
|
||||
with:
|
||||
version: "v0.4.2"
|
||||
- run: cargo build -p kanidm_client -p kanidm_tools --bin kanidm
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- run: cargo build --locked -p kanidm_client -p kanidm_tools --bin kanidm
|
||||
# yamllint disable-line rule:line-length
|
||||
- run: cargo test -p kanidm_client -p kanidm_tools
|
||||
|
|
9
.gitignore
vendored
9
.gitignore
vendored
|
@ -17,9 +17,12 @@ tools/orca/example_profiles/small/orca-edited.toml
|
|||
/docs/
|
||||
# webui things we don't need
|
||||
*.d.ts
|
||||
|
||||
server/web_ui/*/pkg/*.js
|
||||
|
||||
# coverage-related things
|
||||
*.profraw
|
||||
tarpaulin-report.html
|
||||
|
||||
# kanidm simple packaging
|
||||
deployment-config/
|
||||
kanidm_simple_pkg/
|
||||
|
@ -35,9 +38,13 @@ pykanidm/site/
|
|||
# oauth2 integration test things
|
||||
scripts/oauth_proxy/client.secret
|
||||
scripts/oauth_proxy/envfile
|
||||
|
||||
# local config things
|
||||
.envrc
|
||||
|
||||
# IDEs
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# javascript test things
|
||||
node_modules/
|
|
@ -1,2 +1,4 @@
|
|||
# yale-mistakes were made
|
||||
/pykanidm/* @yaleman
|
||||
# Least qualified nix guy :P
|
||||
shell.nix @cebbinghaus
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
- adamcstephens
|
||||
- Chris Olstrom (colstrom)
|
||||
- Christopher-Robin (cebbinghaus)
|
||||
- Fabian Kammel (datosh)
|
||||
- Andris Raugulis (arthepsy)
|
||||
- Jason (argonaut0)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
|
2440
Cargo.lock
generated
2440
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
139
Cargo.toml
139
Cargo.toml
|
@ -1,10 +1,10 @@
|
|||
[workspace.package]
|
||||
version = "1.5.0-dev"
|
||||
version = "1.6.0-dev"
|
||||
authors = [
|
||||
"William Brown <william@blackhats.net.au>",
|
||||
"James Hodgkinson <james@terminaloutcomes.com>",
|
||||
]
|
||||
rust-version = "1.79"
|
||||
rust-version = "1.80"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
homepage = "https://github.com/kanidm/kanidm/"
|
||||
|
@ -120,28 +120,30 @@ codegen-units = 256
|
|||
|
||||
# kanidm-hsm-crypto = { path = "../hsm-crypto" }
|
||||
|
||||
[workspace.dependencies]
|
||||
kanidmd_core = { path = "./server/core", version = "=1.5.0-dev" }
|
||||
kanidmd_lib = { path = "./server/lib", version = "=1.5.0-dev" }
|
||||
kanidmd_lib_macros = { path = "./server/lib-macros", version = "=1.5.0-dev" }
|
||||
kanidmd_testkit = { path = "./server/testkit", version = "=1.5.0-dev" }
|
||||
kanidm_build_profiles = { path = "./libs/profiles", version = "=1.5.0-dev" }
|
||||
kanidm_client = { path = "./libs/client", version = "=1.5.0-dev" }
|
||||
kanidm-hsm-crypto = "^0.2.0"
|
||||
kanidm_lib_crypto = { path = "./libs/crypto", version = "=1.5.0-dev" }
|
||||
kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "=1.5.0-dev" }
|
||||
kanidm_proto = { path = "./proto", version = "=1.5.0-dev" }
|
||||
kanidm_unix_common = { path = "./unix_integration/common", version = "=1.5.0-dev" }
|
||||
kanidm_utils_users = { path = "./libs/users", version = "=1.5.0-dev" }
|
||||
scim_proto = { path = "./libs/scim_proto", version = "=1.5.0-dev" }
|
||||
sketching = { path = "./libs/sketching", version = "=1.5.0-dev" }
|
||||
libnss = { git = "https://github.com/Firstyear/libnss-rs.git", branch = "20250207-freebsd" }
|
||||
|
||||
anyhow = { version = "1.0.90" }
|
||||
[workspace.dependencies]
|
||||
kanidmd_core = { path = "./server/core", version = "=1.6.0-dev" }
|
||||
kanidmd_lib = { path = "./server/lib", version = "=1.6.0-dev" }
|
||||
kanidmd_lib_macros = { path = "./server/lib-macros", version = "=1.6.0-dev" }
|
||||
kanidmd_testkit = { path = "./server/testkit", version = "=1.6.0-dev" }
|
||||
kanidm_build_profiles = { path = "./libs/profiles", version = "=1.6.0-dev" }
|
||||
kanidm_client = { path = "./libs/client", version = "=1.6.0-dev" }
|
||||
kanidm-hsm-crypto = "^0.2.0"
|
||||
kanidm_lib_crypto = { path = "./libs/crypto", version = "=1.6.0-dev" }
|
||||
kanidm_lib_file_permissions = { path = "./libs/file_permissions", version = "=1.6.0-dev" }
|
||||
kanidm_proto = { path = "./proto", version = "=1.6.0-dev" }
|
||||
kanidm_unix_common = { path = "./unix_integration/common", version = "=1.6.0-dev" }
|
||||
kanidm_utils_users = { path = "./libs/users", version = "=1.6.0-dev" }
|
||||
scim_proto = { path = "./libs/scim_proto", version = "=1.6.0-dev" }
|
||||
sketching = { path = "./libs/sketching", version = "=1.6.0-dev" }
|
||||
|
||||
anyhow = { version = "1.0.95" }
|
||||
argon2 = { version = "0.5.3", features = ["alloc"] }
|
||||
askama = { version = "0.12.1", features = ["serde", "with-axum"] }
|
||||
askama_axum = { version = "0.4.0" }
|
||||
async-trait = "^0.1.83"
|
||||
axum = { version = "0.7.7", features = [
|
||||
async-trait = "^0.1.85"
|
||||
axum = { version = "0.7.9", features = [
|
||||
"form",
|
||||
"json",
|
||||
"macros",
|
||||
|
@ -154,41 +156,41 @@ axum = { version = "0.7.7", features = [
|
|||
axum-htmx = { version = "0.5.0", features = ["serde", "guards"] }
|
||||
base32 = "^0.5.1"
|
||||
base64 = "^0.22.1"
|
||||
base64urlsafedata = "0.5.0"
|
||||
bitflags = "^2.6.0"
|
||||
bytes = "^1.7.2"
|
||||
clap = { version = "^4.5.20", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.33"
|
||||
base64urlsafedata = "0.5.1"
|
||||
bitflags = "^2.8.0"
|
||||
bytes = "^1.9.0"
|
||||
clap = { version = "^4.5.34", features = ["derive", "env"] }
|
||||
clap_complete = "^4.5.42"
|
||||
# Forced by saffron/cron
|
||||
chrono = "^0.4.35"
|
||||
chrono = "^0.4.39"
|
||||
compact_jwt = { version = "^0.4.2", default-features = false }
|
||||
concread = "^0.5.3"
|
||||
cron = "0.12.1"
|
||||
concread = "^0.5.5"
|
||||
cron = "0.15.0"
|
||||
crossbeam = "0.8.4"
|
||||
csv = "1.3.0"
|
||||
dialoguer = "0.10.4"
|
||||
csv = "1.3.1"
|
||||
dialoguer = "0.11.0"
|
||||
dhat = "0.3.3"
|
||||
dyn-clone = "^1.0.17"
|
||||
fernet = "^0.2.1"
|
||||
filetime = "^0.2.24"
|
||||
fs4 = "^0.8.3"
|
||||
fs4 = "^0.13.0"
|
||||
futures = "^0.3.31"
|
||||
futures-util = { version = "^0.3.30", features = ["sink"] }
|
||||
gix = { version = "0.64.0", default-features = false }
|
||||
hashbrown = { version = "0.14.3", features = ["serde", "inline-more", "ahash"] }
|
||||
hex = "^0.4.3"
|
||||
http = "1.1.0"
|
||||
hyper = { version = "1.5.0", features = [
|
||||
http = "1.2.0"
|
||||
hyper = { version = "1.5.1", features = [
|
||||
"full",
|
||||
] } # hyper full includes client/server/http2
|
||||
hyper-util = { version = "0.1.9", features = ["server", "tokio"] }
|
||||
hyper-util = { version = "0.1.10", features = ["server", "tokio"] }
|
||||
idlset = "^0.2.5"
|
||||
image = { version = "0.24.9", default-features = false, features = [
|
||||
"gif",
|
||||
"jpeg",
|
||||
"webp",
|
||||
] }
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
enum-iterator = "2.1.0"
|
||||
kanidmd_web_ui_shared = { path = "./server/web_ui/shared" }
|
||||
# REMOVE this
|
||||
|
@ -196,60 +198,63 @@ lazy_static = "^1.5.0"
|
|||
ldap3_client = "^0.5.2"
|
||||
ldap3_proto = { version = "^0.5.2", features = ["serde"] }
|
||||
|
||||
libc = "^0.2.161"
|
||||
libc = "^0.2.168"
|
||||
libnss = "^0.8.0"
|
||||
libsqlite3-sys = "^0.25.2"
|
||||
lodepng = "3.10.7"
|
||||
lru = "^0.12.5"
|
||||
lodepng = "3.11.0"
|
||||
lru = "^0.13.0"
|
||||
mathru = "^0.13.0"
|
||||
md-5 = "0.10.6"
|
||||
mimalloc = "0.1.43"
|
||||
notify-debouncer-full = { version = "0.1" }
|
||||
notify-debouncer-full = { version = "0.5" }
|
||||
num_enum = "^0.5.11"
|
||||
oauth2_ext = { version = "^4.4.2", package = "oauth2", default-features = false }
|
||||
openssl-sys = "^0.9"
|
||||
openssl = "^0.10.68"
|
||||
openssl = "^0.10.72"
|
||||
|
||||
opentelemetry = { version = "0.20.0" }
|
||||
opentelemetry_api = { version = "0.20.0", features = ["logs", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.13.0", default-features = false, features = [
|
||||
opentelemetry = { version = "0.27.0" }
|
||||
opentelemetry_api = { version = "0.27.0", features = ["logs", "metrics"] }
|
||||
opentelemetry-otlp = { version = "0.27.0", default-features = false, features = [
|
||||
"serde",
|
||||
"logs",
|
||||
"metrics",
|
||||
"http-proto",
|
||||
"grpc-tonic",
|
||||
] }
|
||||
opentelemetry_sdk = "0.20.0"
|
||||
tracing-opentelemetry = "0.21.0"
|
||||
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = "0.27.0"
|
||||
tracing-opentelemetry = "0.28.0"
|
||||
tracing-core = "0.1.33"
|
||||
|
||||
paste = "^1.0.14"
|
||||
peg = "0.8"
|
||||
pkg-config = "^0.3.31"
|
||||
prctl = "1.0.0"
|
||||
proc-macro2 = "1.0.88"
|
||||
proc-macro2 = "1.0.93"
|
||||
qrcode = "^0.12.0"
|
||||
quote = "1"
|
||||
rand = "^0.8.5"
|
||||
rand_chacha = "0.3.1"
|
||||
regex = "1.11.0"
|
||||
reqwest = { version = "0.12.8", default-features = false, features = [
|
||||
reqwest = { version = "0.12.12", default-features = false, features = [
|
||||
"cookies",
|
||||
"http2",
|
||||
"json",
|
||||
"gzip",
|
||||
"rustls-tls-native-roots",
|
||||
"rustls-tls-native-roots-no-provider",
|
||||
] }
|
||||
rusqlite = { version = "^0.28.0", features = ["array", "bundled"] }
|
||||
rustls = { version = "0.23.13", default-features = false, features = [
|
||||
rustls = { version = "0.23.21", default-features = false, features = [
|
||||
"aws_lc_rs",
|
||||
] }
|
||||
|
||||
sd-notify = "^0.4.3"
|
||||
sd-notify = "^0.4.5"
|
||||
selinux = "^0.4.6"
|
||||
serde = "^1.0.210"
|
||||
serde = "^1.0.217"
|
||||
serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" }
|
||||
serde_json = "^1.0.132"
|
||||
serde_json = "^1.0.137"
|
||||
serde_urlencoded = "^0.7.1"
|
||||
serde_with = "3.11.0"
|
||||
serde_with = "3.12.0"
|
||||
sha-crypt = "0.5.0"
|
||||
sha2 = "0.10.8"
|
||||
shellexpand = "^2.1.2"
|
||||
|
@ -258,39 +263,38 @@ smolset = "^1.3.1"
|
|||
sshkey-attest = "^0.5.0"
|
||||
sshkeys = "0.3.3"
|
||||
svg = "0.13.1"
|
||||
syn = { version = "2.0.82", features = ["full"] }
|
||||
tempfile = "3.13.0"
|
||||
syn = { version = "2.0.96", features = ["full"] }
|
||||
tempfile = "3.15.0"
|
||||
testkit-macros = { path = "./server/testkit-macros" }
|
||||
time = { version = "^0.3.34", features = ["formatting", "local-offset"] }
|
||||
time = { version = "^0.3.36", features = ["formatting", "local-offset"] }
|
||||
|
||||
tokio = "^1.40.0"
|
||||
tokio = "^1.44.2"
|
||||
tokio-openssl = "^0.6.5"
|
||||
tokio-util = "^0.7.12"
|
||||
tokio-util = "^0.7.13"
|
||||
|
||||
toml = "^0.5.11"
|
||||
tracing = { version = "^0.1.40", features = [
|
||||
tracing = { version = "^0.1.41", features = [
|
||||
"max_level_trace",
|
||||
"release_max_level_debug",
|
||||
] }
|
||||
tracing-subscriber = { version = "^0.3.18", features = ["env-filter"] }
|
||||
tracing-subscriber = { version = "^0.3.19", features = ["env-filter"] }
|
||||
tracing-forest = "^0.1.6"
|
||||
|
||||
url = "^2.5.2"
|
||||
urlencoding = "2.1.3"
|
||||
utoipa = { version = "4.2.0", features = ["url", "uuid"] }
|
||||
utoipa-swagger-ui = "6.0.0"
|
||||
uuid = "^1.11.0"
|
||||
uuid = "^1.12.1"
|
||||
|
||||
webauthn-authenticator-rs = { version = "0.5.0", features = [
|
||||
webauthn-authenticator-rs = { version = "0.5.1", features = [
|
||||
"softpasskey",
|
||||
"softtoken",
|
||||
"mozilla",
|
||||
] }
|
||||
webauthn-rs = { version = "0.5.0", features = ["preview-features"] }
|
||||
webauthn-rs-core = "0.5.0"
|
||||
webauthn-rs-proto = "0.5.0"
|
||||
webauthn-rs = { version = "0.5.1", features = ["preview-features"] }
|
||||
webauthn-rs-core = "0.5.1"
|
||||
webauthn-rs-proto = "0.5.1"
|
||||
|
||||
whoami = "^1.5.2"
|
||||
whoami = "^1.6.0"
|
||||
walkdir = "2"
|
||||
|
||||
x509-cert = "0.2.5"
|
||||
|
@ -298,4 +302,3 @@ x509-cert = "0.2.5"
|
|||
zxcvbn = "^2.2.2"
|
||||
|
||||
nonempty = "0.8.1"
|
||||
|
||||
|
|
67
Makefile
67
Makefile
|
@ -178,11 +178,8 @@ codespell:
|
|||
--skip='*.svg' \
|
||||
--skip='*.br' \
|
||||
--skip='./rlm_python/mods-available/eap' \
|
||||
--skip='./server/web_ui/static/external' \
|
||||
--skip='./server/web_ui/pkg/external' \
|
||||
--skip='./server/web_ui/shared/static/external' \
|
||||
--skip='./server/web_ui/admin/static/external,./server/web_ui/admin/pkg/external' \
|
||||
--skip='./server/lib/src/constants/system_config.rs,./pykanidm/site,./server/lib/src/constants/*.json'
|
||||
--skip='./server/lib/src/constants/system_config.rs' \
|
||||
--skip='./pykanidm/site'
|
||||
|
||||
.PHONY: test/pykanidm/pytest
|
||||
test/pykanidm/pytest: ## python library testing
|
||||
|
@ -316,26 +313,44 @@ cert/clean:
|
|||
rm -f /tmp/kanidm/ca.txt*
|
||||
rm -f /tmp/kanidm/ca.{cnf,srl,srl.old}
|
||||
|
||||
.PHONY: rust/coverage
|
||||
coverage/test: ## Run coverage tests
|
||||
coverage/test:
|
||||
LLVM_PROFILE_FILE="$(PWD)/target/profile/coverage-%p-%m.profraw" RUSTFLAGS="-C instrument-coverage" cargo test $(TESTS)
|
||||
|
||||
.PHONY: coverage/grcov
|
||||
coverage/grcov: ## Run grcov
|
||||
coverage/grcov:
|
||||
rm -rf ./target/coverage/html
|
||||
grcov . --binary-path ./target/debug/deps/ \
|
||||
-s . \
|
||||
-t html \
|
||||
--branch \
|
||||
--ignore-not-existing \
|
||||
--ignore '../*' \
|
||||
--ignore "/*" \
|
||||
--ignore "target/*" \
|
||||
-o target/coverage/html
|
||||
|
||||
.PHONY: coverage
|
||||
coverage: ## Run all the coverage tests
|
||||
coverage: coverage/test coverage/grcov
|
||||
echo "Coverage report is in ./target/coverage/html/index.html"
|
||||
coverage: ## Run the coverage tests using cargo-tarpaulin
|
||||
cargo tarpaulin --out Html
|
||||
@echo "Coverage file at file://$(PWD)/tarpaulin-report.html"
|
||||
|
||||
|
||||
.PHONY: coveralls
|
||||
coveralls: ## Run cargo tarpaulin and upload to coveralls
|
||||
coveralls:
|
||||
cargo tarpaulin --coveralls $(COVERALLS_REPO_TOKEN)
|
||||
@echo "Coveralls repo information is at https://coveralls.io/github/kanidm/kanidm"
|
||||
|
||||
|
||||
.PHONY: eslint
|
||||
eslint: ## Run eslint on the UI javascript things
|
||||
eslint: eslint/setup
|
||||
@echo "################################"
|
||||
@echo " Running eslint..."
|
||||
@echo "################################"
|
||||
cd server/core && find ./static -name '*js' -not -path '*/external/*' -exec eslint "{}" \;
|
||||
@echo "################################"
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: eslint/setup
|
||||
eslint/setup: ## Install eslint for the UI javascript things
|
||||
cd server/core && npm ci
|
||||
|
||||
.PHONY: prettier
|
||||
prettier: ## Run prettier on the UI javascript things
|
||||
prettier: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier
|
||||
@echo "Done!"
|
||||
|
||||
.PHONY: prettier/fix
|
||||
prettier/fix: ## Run prettier on the UI javascript things and write back changes
|
||||
prettier/fix: eslint/setup
|
||||
@echo " Running prettier..."
|
||||
cd server/core && npm run prettier:fix
|
||||
@echo "Done!"
|
|
@ -1,8 +1,6 @@
|
|||
# Kanidm - Simple and Secure Identity Management
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/kanidm/kanidm/master/artwork/logo-small.png" width="20%" height="auto" />
|
||||
</p>
|
||||

|
||||
|
||||
## About
|
||||
|
||||
|
|
159
RELEASE_NOTES.md
159
RELEASE_NOTES.md
|
@ -1,42 +1,93 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/kanidm/kanidm/master/artwork/logo-small.png" width="20%" height="auto" />
|
||||
</p>
|
||||
# Kanidm Release Notes
|
||||
|
||||
# Getting Started
|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
To get started, see the [kanidm book]
|
||||
|
||||
# Feedback
|
||||
## Feedback
|
||||
|
||||
We value your feedback! First, please see our [code of conduct]. If you have questions please join
|
||||
our [gitter community channel] so that we can help. If you find a bug or issue, we'd love you to
|
||||
report it to our [issue tracker].
|
||||
|
||||
# Release Notes
|
||||
## Release Notes
|
||||
|
||||
## 2024-08-07 - Kanidm 1.4.0
|
||||
### 2025-02-09 - Kanidm 1.5.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md) as this
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes)
|
||||
[our upgrade documentation]
|
||||
|
||||
### 1.4.0 Important Changes
|
||||
#### 1.5.0 Important Changes
|
||||
|
||||
- There has been a lot of tweaks to how cookies are handled in this release, if you're having issues with the login flow please clear all cookies as an initial troubleshooting step.
|
||||
|
||||
#### 1.5.0 Release Highlights
|
||||
|
||||
- Many updates to the UI!
|
||||
- SSH Keys in Credentials Update (#3027)
|
||||
- Improved error message when PassKey is missing PIN (mainly for Firefox) (#3403)
|
||||
- Fix the password reset form and possible resolver issue (#3398)
|
||||
- Fixed unrecoverable error page doesn't include logo or domain name (#3352)
|
||||
- Add support for prefers-color-scheme using Bootstrap classes. Dark mode! (#3327)
|
||||
- Automatically trigger passkeys on login view (#3307)
|
||||
- Two new operating systems!
|
||||
- Initial OpenBSD support (#3381)
|
||||
- FreeBSD client (#3333)
|
||||
- Many SCIM-related improvements
|
||||
- SCIM access control (#3359)
|
||||
- SCIM put (#3151)
|
||||
- OAuth2 Things
|
||||
- Allow OAuth2 with empty `state` parameter (#3396)
|
||||
- Allow POST on oauth userinfo (#3395)
|
||||
- Add OAuth2 `response_mode=fragment` (#3335)
|
||||
- Add CORS headers to jwks and userinfo (#3283)
|
||||
- Allowing SPN query with non-SPN structured data in LDAP (#3400)
|
||||
- Correctly return that uuid2spn changed on domain rename (#3402)
|
||||
- RADIUS startup fixing (#3388)
|
||||
- Repaired systemd reload notifications (#3355)
|
||||
- Add `ssh_publickeys` as a claim for OAuth2 (#3346)
|
||||
- Allow modification of password minimum length (#3345)
|
||||
- PAM on Debian, enable use_first_pass by default (#3326)
|
||||
- Allow opt-in of easter eggs (#3308)
|
||||
- Allow reseting account policy values to defaults (#3306)
|
||||
- Ignore system users for UPG synthesiseation (#3297)
|
||||
- Allow group managers to modify entry-managed-by (#3272)
|
||||
|
||||
And many more!
|
||||
|
||||
### 2024-11-01 - Kanidm 1.4.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation]
|
||||
|
||||
#### 1.4.0 Important Changes
|
||||
|
||||
- The web user interface has been rewritten and now supports theming. You will notice that your
|
||||
domain displayname is included in a number of locations on upgrade, and that you can set
|
||||
your own domain and OAuth2 client icons.
|
||||
- OAuth2 strict redirect uri is now required. Ensure you have read
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes).
|
||||
[our upgrade documentation].
|
||||
and taken the needed steps before upgrading.
|
||||
|
||||
### 1.4.0 Release Highlights
|
||||
#### 1.4.0 Release Highlights
|
||||
|
||||
- Improve handling of client timeouts when the server is under high load
|
||||
- Resolve a minor issue preventing some credential updates from saving
|
||||
|
@ -63,22 +114,22 @@ and taken the needed steps before upgrading.
|
|||
- SCIM foundations for getting and modifying entries, reference handling, and complex attribute
|
||||
display. Much more to come in this space!
|
||||
- Rewrite the entire web frontend to be simpler and faster, allowing more features to be added
|
||||
in future. Greatly improves user expirence as the pages are now very fast to load!
|
||||
in the future. Greatly improves user experience as the pages are now very fast to load!
|
||||
|
||||
## 2024-08-07 - Kanidm 1.3.0
|
||||
### 2024-08-07 - Kanidm 1.3.0
|
||||
|
||||
This is the latest stable release of the Kanidm Identity Management project. Every release is the
|
||||
combined effort of our community and we appreciate their invaluable contributions, comments,
|
||||
questions, feedback and support.
|
||||
|
||||
You should review our
|
||||
[support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md) as this
|
||||
[support documentation] as this
|
||||
may have important effects on your distribution or upgrades in future.
|
||||
|
||||
Before upgrading you should review
|
||||
[our upgrade documentation](https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes)
|
||||
[our upgrade documentation]
|
||||
|
||||
### 1.3.0 Important Changes
|
||||
#### 1.3.0 Important Changes
|
||||
|
||||
- New GID number constraints are now enforced in this version. To upgrade from 1.2.0 all accounts
|
||||
and groups must adhere to these rules. See [our upgrade documentation]. about tools to help you
|
||||
|
@ -89,7 +140,7 @@ Before upgrading you should review
|
|||
by PassKeys which give a better user experience.
|
||||
- Kanidm now supports FreeBSD and Illumos in addition to Linux
|
||||
|
||||
### 1.3.0 Release Highlights
|
||||
#### 1.3.0 Release Highlights
|
||||
|
||||
- TOTP update user interface improvements
|
||||
- Improved error messages when a load balancer is failing
|
||||
|
@ -112,24 +163,24 @@ Before upgrading you should review
|
|||
- Strict redirect URI enforcement in OAuth2
|
||||
- Substring indexing for improved search performance
|
||||
|
||||
## 2024-05-01 - Kanidm 1.2.0
|
||||
### 2024-05-01 - Kanidm 1.2.0
|
||||
|
||||
This is the first stable release of the Kanidm Identity Management project. We want to thank every
|
||||
one in our community who has supported to the project to this point with their invaluable
|
||||
contributions, comments, questions, feedback and support.
|
||||
|
||||
Importantly this release makes a number of changes to our project's support processes. You should
|
||||
review our [support documentation](https://github.com/kanidm/kanidm/blob/master/book/src/support.md)
|
||||
review our [support documentation]
|
||||
as this may have important effects on your distribution or upgrades in future.
|
||||
|
||||
### 1.2.0 Important Changes
|
||||
#### 1.2.0 Important Changes
|
||||
|
||||
- On upgrade all OAuth2 sessions and user sessions will be reset due to changes in cryptographic key
|
||||
handling. This does not affect api tokens.
|
||||
- There is a maximum limit of 48 interactive sessions for persons where older sessions are
|
||||
automatically removed.
|
||||
|
||||
### 1.2.0 Release Highlights
|
||||
#### 1.2.0 Release Highlights
|
||||
|
||||
- The book now contains a list of supported RFCs and standards
|
||||
- Add code challenge methods to OIDC discovery
|
||||
|
@ -154,7 +205,7 @@ as this may have important effects on your distribution or upgrades in future.
|
|||
- Migrate cryptographic key handling to an object model with future HSM support
|
||||
- Limit maximum active sessions on an account to 48
|
||||
|
||||
## 2024-02-07 - Kanidm 1.1.0-rc.16
|
||||
### 2024-02-07 - Kanidm 1.1.0-rc.16
|
||||
|
||||
This is the sixteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -163,7 +214,7 @@ This is the final release candidate before we publish a release version. We beli
|
|||
server interfaces are stable and reliable enough for people to depend on, and to develop external
|
||||
tools to interact with Kanidm.
|
||||
|
||||
### 1.1.0-rc.16 Release Highlights
|
||||
#### 1.1.0-rc.16 Release Highlights
|
||||
|
||||
- Replication for two node environments is now supported
|
||||
- Account policy supports password minimum length
|
||||
|
@ -182,7 +233,7 @@ tools to interact with Kanidm.
|
|||
- Support RFC6749 Client Credentials Grant
|
||||
- Support custom claim maps in OIDC
|
||||
|
||||
## 2023-10-31 - Kanidm 1.1.0-beta14
|
||||
### 2023-10-31 - Kanidm 1.1.0-beta14
|
||||
|
||||
This is the fourteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -191,7 +242,7 @@ At this point we believe we are on the final stretch to making something we cons
|
|||
ready". After this we will start to ship release candidates as our focus will now be changing to
|
||||
finish our production components and the stability of the API's for longer term support.
|
||||
|
||||
### 1.1.0-beta14 Release Highlights
|
||||
#### 1.1.0-beta14 Release Highlights
|
||||
|
||||
- Replication is in Beta! Please test carefully!
|
||||
- Web UI WASM has been split up, significantly improving the responsiveness.
|
||||
|
@ -205,7 +256,7 @@ finish our production components and the stability of the API's for longer term
|
|||
- Removed a lot of uses of `unwrap` and `expect` to improve reliability.
|
||||
- Account policy framework is now in place.
|
||||
|
||||
## 2023-05-01 - Kanidm 1.1.0-beta13
|
||||
### 2023-05-01 - Kanidm 1.1.0-beta13
|
||||
|
||||
This is the thirteenth pre-release of the Kanidm Identity Management project. Pre-releases are to
|
||||
help get feedback and ideas from the community on how we can continue to make this project better.
|
||||
|
@ -214,7 +265,7 @@ At this point we believe we are on the final stretch to making something we cons
|
|||
ready". After this we will start to ship release candidates as our focus will now be changing to
|
||||
finish our production components and the stability of the API's for longer term support.
|
||||
|
||||
### 1.1.0-beta13 Release Highlights
|
||||
#### 1.1.0-beta13 Release Highlights
|
||||
|
||||
- Replication foundations
|
||||
- Full implementation of replication refresh
|
||||
|
@ -255,7 +306,7 @@ finish our production components and the stability of the API's for longer term
|
|||
- Improve create-reset-token user experience
|
||||
- Improve self-healing for some reference issues
|
||||
|
||||
## 2023-05-01 - Kanidm 1.1.0-alpha12
|
||||
### 2023-05-01 - Kanidm 1.1.0-alpha12
|
||||
|
||||
This is the twelfth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
|
@ -266,7 +317,7 @@ done so yet is we haven't decided if we want to commit to the current API layout
|
|||
There are still things we want to change there. Otherwise the server is stable and reliable for
|
||||
production usage.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha12 Release Highlights
|
||||
|
||||
- Allow full server content replication in testing (yes we're finally working on replication!)
|
||||
- Improve OAuth2 to allow scoped members to see RS they can access for UI flows
|
||||
|
@ -286,7 +337,7 @@ production usage.
|
|||
- Add exclusive process lock to daemon
|
||||
- Allow dns/rdns in ldap search contexts
|
||||
|
||||
## 2023-02-01 - Kanidm 1.1.0-alpha11
|
||||
### 2023-02-01 - Kanidm 1.1.0-alpha11
|
||||
|
||||
This is the eleventh alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
|
@ -296,7 +347,7 @@ The project is shaping up very nicely, and a beta will be coming soon! The main
|
|||
done so yet is we haven't decided if we want to commit to the current API layout and freeze it yet.
|
||||
There are still things we want to change there. Otherwise the server is stable and reliable.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha11 Release Highlights
|
||||
|
||||
- Support /etc/skel home dir templates in kanidm-unixd
|
||||
- Improve warning messages for openssl when a cryptographic routine is not supported
|
||||
|
@ -317,7 +368,7 @@ There are still things we want to change there. Otherwise the server is stable a
|
|||
- Improve the access control module to evaluate access in a clearer way
|
||||
- Allow synced users to correct modify their local sessions
|
||||
|
||||
## 2022-11-01 - Kanidm 1.1.0-alpha10
|
||||
### 2022-11-01 - Kanidm 1.1.0-alpha10
|
||||
|
||||
This is the tenth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -325,12 +376,12 @@ for a future supported release.
|
|||
|
||||
The project is shaping up very nicely, and a beta will be coming soon!
|
||||
|
||||
### Upgrade Note
|
||||
#### 1.1.0-alpha10 Upgrade Note
|
||||
|
||||
This version will _require_ TLS on all servers, even if behind a load balancer or TLS terminating
|
||||
proxy. You should be ready for this change when you upgrade to the latest version.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha10 Release Highlights
|
||||
|
||||
- Management and tracking of authenticated sessions
|
||||
- Make upgrade migrations more robust when upgrading over multiple versions
|
||||
|
@ -352,7 +403,7 @@ proxy. You should be ready for this change when you upgrade to the latest versio
|
|||
- Cleanup of expired authentication sessions
|
||||
- Improved administration of password badlists
|
||||
|
||||
## 2022-08-02 - Kanidm 1.1.0-alpha9
|
||||
### 2022-08-02 - Kanidm 1.1.0-alpha9
|
||||
|
||||
This is the ninth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -360,7 +411,7 @@ for a future supported release.
|
|||
|
||||
The project is shaping up very nicely, and a beta will be coming soon!
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha9 Release Highlights
|
||||
|
||||
- Inclusion of a Python3 API library
|
||||
- Improve orca usability
|
||||
|
@ -376,13 +427,13 @@ The project is shaping up very nicely, and a beta will be coming soon!
|
|||
- CTAP2+ support in Webauthn via CLI
|
||||
- Radius supports EAP TLS identities in addition to EAP PEAP
|
||||
|
||||
## 2022-05-01 - Kanidm 1.1.0-alpha8
|
||||
### 2022-05-01 - Kanidm 1.1.0-alpha8
|
||||
|
||||
This is the eighth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha8 Release Highlights
|
||||
|
||||
- Foundations for cryptographic trusted device authentication
|
||||
- Foundations for new user onboarding and credential reset
|
||||
|
@ -398,13 +449,13 @@ better for a future supported release.
|
|||
- Highlight that the WebUI is in alpha to prevent confusion
|
||||
- Remove sync only client paths
|
||||
|
||||
## 2022-01-01 - Kanidm 1.1.0-alpha7
|
||||
### 2022-01-01 - Kanidm 1.1.0-alpha7
|
||||
|
||||
This is the seventh alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha7 Release Highlights
|
||||
|
||||
- OAuth2 scope to group mappings
|
||||
- Webauthn subdomain support
|
||||
|
@ -415,7 +466,7 @@ better for a future supported release.
|
|||
- Addition of email address attributes
|
||||
- Web UI improvements for OAuth2
|
||||
|
||||
## 2021-10-01 - Kanidm 1.1.0-alpha6
|
||||
### 2021-10-01 - Kanidm 1.1.0-alpha6
|
||||
|
||||
This is the sixth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -424,7 +475,7 @@ for a future supported release.
|
|||
It's also a special release as Kanidm has just turned 3 years old! Thank you all for helping to
|
||||
bring the project this far! 🎉 🦀
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha6 Release Highlights
|
||||
|
||||
- Support backup codes as MFA in case of lost TOTP/Webauthn
|
||||
- Dynamic menus on CLI for usernames when multiple sessions exist
|
||||
|
@ -444,13 +495,13 @@ bring the project this far! 🎉 🦀
|
|||
- Improvements to performance with high cache sizes
|
||||
- Session tokens persist over a session restart
|
||||
|
||||
## 2021-07-07 - Kanidm 1.1.0-alpha5
|
||||
### 2021-07-07 - Kanidm 1.1.0-alpha5
|
||||
|
||||
This is the fifth alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha5 Release Highlights
|
||||
|
||||
- Fix a major defect in how backup/restore worked
|
||||
- Improve query performance by caching partial queries
|
||||
|
@ -465,13 +516,13 @@ for a future supported release.
|
|||
- Statistical analysis of indexes to improve query optimisation
|
||||
- Handle broken TOTP authenticator apps
|
||||
|
||||
## 2021-04-01 - Kanidm 1.1.0-alpha4
|
||||
### 2021-04-01 - Kanidm 1.1.0-alpha4
|
||||
|
||||
This is the fourth alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha4 Release Highlights
|
||||
|
||||
- Performance Improvements
|
||||
- TOTP CLI enrollment
|
||||
|
@ -485,13 +536,13 @@ better for a future supported release.
|
|||
- Badlist checked at login to determine account compromise
|
||||
- Minor Fixes for attribute display
|
||||
|
||||
## 2021-01-01 - Kanidm 1.1.0-alpha3
|
||||
### 2021-01-01 - Kanidm 1.1.0-alpha3
|
||||
|
||||
This is the third alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha3 Release Highlights
|
||||
|
||||
- Account "valid from" and "expiry" times.
|
||||
- Rate limiting and softlocking of account credentials to prevent bruteforcing.
|
||||
|
@ -499,13 +550,13 @@ for a future supported release.
|
|||
- Rewrite of json authentication protocol components.
|
||||
- Unixd will cache "non-existent" items to improve nss/pam latency.
|
||||
|
||||
## 2020-10-01 - Kanidm 1.1.0-alpha2
|
||||
### 2020-10-01 - Kanidm 1.1.0-alpha2
|
||||
|
||||
This is the second alpha series release of the Kanidm Identity Management project. Alpha releases
|
||||
are to help get feedback and ideas from the community on how we can continue to make this project
|
||||
better for a future supported release.
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha2 Release Highlights
|
||||
|
||||
- SIMD key lookups in container builds for datastructures
|
||||
- Server and Client hardening warnings for running users and file permissions
|
||||
|
@ -517,7 +568,7 @@ better for a future supported release.
|
|||
- Reduction in memory footprint during searches
|
||||
- Change authentication from cookies to auth-bearer tokens
|
||||
|
||||
## 2020-07-01 - Kanidm 1.1.0-alpha1
|
||||
### 2020-07-01 - Kanidm 1.1.0-alpha1
|
||||
|
||||
This is the first alpha series release of the Kanidm Identity Management project. Alpha releases are
|
||||
to help get feedback and ideas from the community on how we can continue to make this project better
|
||||
|
@ -536,7 +587,7 @@ people. I would especially like to thank:
|
|||
- Samuel Cabrero (scabrero)
|
||||
- Jim McDonough
|
||||
|
||||
### Release Highlights
|
||||
#### 1.1.0-alpha1 Release Highlights
|
||||
|
||||
- A working identity management server, including database
|
||||
- RADIUS authentication and docker images
|
||||
|
@ -552,3 +603,5 @@ people. I would especially like to thank:
|
|||
[gitter community channel]: https://gitter.im/kanidm/community
|
||||
[code of conduct]: https://github.com/kanidm/kanidm/blob/master/CODE_OF_CONDUCT.md
|
||||
[kanidm book]: https://kanidm.github.io/kanidm/stable/
|
||||
[our upgrade documentation]: https://github.com/kanidm/kanidm/blob/master/book/src/server_updates.md#general-update-notes
|
||||
[support documentation]: https://github.com/kanidm/kanidm/blob/master/book/src/support.md
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
- [Domain Rename](domain_rename.md)
|
||||
- [Monitoring the platform](monitoring_the_platform.md)
|
||||
- [Recycle Bin](recycle_bin.md)
|
||||
- [Customising](customising.md)
|
||||
|
||||
- [Accounts and Groups](accounts/intro.md)
|
||||
- [People Accounts](accounts/people_accounts.md)
|
||||
|
@ -39,9 +40,9 @@
|
|||
- [Service Integrations](integrations/readme.md)
|
||||
- [LDAP](integrations/ldap.md)
|
||||
- [OAuth2](integrations/oauth2.md)
|
||||
- [How does OAuth2 work?](integrations/oauth2/how_does_oauth2_work.md)
|
||||
- [Custom Claims](integrations/oauth2/custom_claims.md)
|
||||
- [Example Configurations](integrations/oauth2/examples.md)
|
||||
- [How does OAuth2 work?](integrations/oauth2/how_does_oauth2_work.md)
|
||||
- [PAM and nsswitch](integrations/pam_and_nsswitch.md)
|
||||
- [SUSE / OpenSUSE](integrations/pam_and_nsswitch/suse.md)
|
||||
- [Fedora](integrations/pam_and_nsswitch/fedora.md)
|
||||
|
@ -52,7 +53,6 @@
|
|||
|
||||
- [Service Integration Examples](examples/readme.md)
|
||||
- [Kubernetes Ingress](examples/kubernetes_ingress.md)
|
||||
- [OAuth2 Examples](integrations/oauth2/examples.md)
|
||||
- [Traefik](examples/traefik.md)
|
||||
|
||||
- [Replication](repl/readme.md)
|
||||
|
|
|
@ -31,6 +31,8 @@ weakest to strongest:
|
|||
- `passkey`
|
||||
- `attested_passkey`
|
||||
|
||||
`attested_passkey` requires [configuring an allowlist of trusted authenticators](#setting-webauthn-attestation-ca-lists).
|
||||
|
||||
### Password Minimum Length
|
||||
|
||||
The minimum length for passwords (if they are allowed).
|
||||
|
@ -45,7 +47,7 @@ read/write session.
|
|||
The list of certificate authorities and device aaguids that must be used by members of this policy.
|
||||
This allows limiting devices to specific models.
|
||||
|
||||
To generate this list you should use `fido-mds-tool`.
|
||||
To generate this list you should [use `fido-mds-tool`](#setting-webauthn-attestation-ca-lists).
|
||||
|
||||
## Policy Resolution
|
||||
|
||||
|
@ -149,15 +151,59 @@ kanidm group account-policy privilege-expiry my_admin_group 86400 # NB: will be
|
|||
|
||||
### Setting Webauthn Attestation CA Lists
|
||||
|
||||
The list should be generated with `fido-mds-tool`. This will emit JSON that can be directly used
|
||||
with Kanidm.
|
||||
To verify Webauthn authenticators with attestation, Kanidm needs an allowlist of
|
||||
authenticators to trust. Generate this list with the `fido-mds-tool` from
|
||||
the [webauthn-rs project](https://github.com/kanidm/webauthn-rs). If you have a
|
||||
Rust toolchain installed, it can built and installed from source with
|
||||
|
||||
```bash
|
||||
kanidm group account-policy webauthn-attestation-ca-list <group name> <attestation ca list json>
|
||||
kanidm group account-policy webauthn-attestation-ca-list idm_all_persons '{"cas":{"D6E4b4Drh .... }'
|
||||
cargo install fido-mds-tool
|
||||
```
|
||||
|
||||
> NOTE: `fido-mds-tool` is available in the `kanidm:tools` container.
|
||||
Alternatively, `fido-mds-tool` is available in the
|
||||
[tools container](../installing_client_tools.md#tools-container).
|
||||
|
||||
First, fetch the MDS data provided by the FIDO Alliance:
|
||||
```bash
|
||||
fido-mds-tool fetch
|
||||
```
|
||||
|
||||
Then, query the MDS data to generate your allowlist of authenticators.
|
||||
For example, to trust all authenticators made by Yubico, run
|
||||
|
||||
```bash
|
||||
fido-mds-tool query --output-cert-roots "desc cnt yubikey" > trusted-authenticators
|
||||
```
|
||||
|
||||
For details of how to query the MDS data, run
|
||||
|
||||
```bash
|
||||
fido-mds-tool query --help
|
||||
```
|
||||
|
||||
Once you have generated the authenticator allowlist, use it to configure Kanidm's
|
||||
account policy for a group. For example, to set the allowlist for all persons, run
|
||||
|
||||
```bash
|
||||
kanidm group account-policy webauthn-attestation-ca-list idm_all_persons trusted-authenticators
|
||||
```
|
||||
|
||||
### Setting Primary Credential Fallback
|
||||
|
||||
The primary credential fallback enables behavior which allows authenticating
|
||||
using the primary account password when logging in via LDAP.
|
||||
|
||||
If both an LDAP and primary password are specified, Kanidm will only accept the LDAP password.
|
||||
|
||||
```bash
|
||||
kanidm group account-policy allow-primary-cred-fallback <group name> <enabled>
|
||||
```
|
||||
|
||||
to disable it for a group you would run:
|
||||
|
||||
```bash
|
||||
kanidm group account-policy allow-primary-cred-fallback <group name> false
|
||||
```
|
||||
|
||||
## Global Settings
|
||||
|
||||
|
|
|
@ -110,9 +110,9 @@ These validity settings impact all authentication functions of the account (kani
|
|||
|
||||
By default, Kanidm allows an account to change some attributes, but not their mail address.
|
||||
|
||||
Adding the user to the `idm_people_self_write_mail` group, as shown below, allows the user to edit
|
||||
Adding the user to the `idm_people_self_mail_write` group, as shown below, allows the user to edit
|
||||
their own mail.
|
||||
|
||||
```bash
|
||||
kanidm group add-members idm_people_self_write_mail demo_user --name idm_admin
|
||||
kanidm group add-members idm_people_self_mail_write demo_user --name idm_admin
|
||||
```
|
||||
|
|
|
@ -58,6 +58,21 @@ can only use the UID range `1879048192` (`0x70000000`) to `2147483647` (`0x7ffff
|
|||
limitations of the Linux kernel and
|
||||
[systemd reserving other uids in the range](http://systemd.io/UIDS-GIDS/) for its exclusive use.
|
||||
|
||||
| name | min | max |
|
||||
|------|-----|-----|
|
||||
| system | 0 | 999 |
|
||||
| user | 1000 | 60000 |
|
||||
| systemd homed | 60001 | 60577 |
|
||||
| unused A | 60578 | 61183 |
|
||||
| systemd dynuser | 61184 | 65519 |
|
||||
| unused B | 65520 | 65533 |
|
||||
| nobody | 65534 | 65534 |
|
||||
| 16bit limit | 65535 | 65535 |
|
||||
| unused C | 65536 | 524287 |
|
||||
| systemd nspawn | 524288 | 1879048191 |
|
||||
| kanidm dyn alloc | 1879048192 | 2147483647 |
|
||||
| unusable | 2147483648 | 4294967295 |
|
||||
|
||||
A valid concern is the possibility of duplication in the lower 24 bits. Given the
|
||||
[birthday problem](https://en.wikipedia.org/wiki/Birthday_problem), if you have ~7700 groups and
|
||||
accounts, you have a 50% chance of duplication. With ~5000 you have a 25% chance, ~930 you have a 1%
|
||||
|
@ -67,7 +82,7 @@ We advise that if you have a site with greater than approximately 2,000 users yo
|
|||
external system to allocate GID numbers serially or consistently to avoid potential duplication
|
||||
events.
|
||||
|
||||
We recommend the use of the range `65536` through `524287` for manual allocation. This leaves the
|
||||
We recommend the use of the range `65536` through `524287` (`unused C`) for manual allocation. This leaves the
|
||||
range `1000` through `65535` for OS/Distro purposes, and allows Kanidm to continue dynamic
|
||||
allocation in the range `1879048192` to `2147483647` if you choose a hybrid allocation approach.
|
||||
|
||||
|
|
|
@ -11,14 +11,15 @@ when choosing a domain.
|
|||
> **Bad choices** of domain name may have security impacts on your Kanidm
|
||||
> instance, not limited to credential phishing, theft, session leaks and more.
|
||||
>
|
||||
> **Changing** domain name is hard to do – it not only means reconfiguring all
|
||||
> LDAP and OAuth clients, but will also break all registered WebAuthn
|
||||
> credentials for all users (which are bound to an `Origin`).
|
||||
> [**Changing** domain name is hard to do](./domain_rename.md) – it not only
|
||||
> means reconfiguring all LDAP and OAuth clients, but will also break all
|
||||
> registered WebAuthn credentials for all users (which are bound to an
|
||||
> `Origin`).
|
||||
>
|
||||
> It's critical that you consider and follow the advice in this chapter, and
|
||||
> aim to get it right the first time.
|
||||
>
|
||||
> You'll save yourself a lot of work later!
|
||||
> You'll save yourself (and your users) a lot of work later!
|
||||
|
||||
<!-- -->
|
||||
|
||||
|
@ -89,7 +90,7 @@ country cease to exist (eg: [as for `.io`][dot-io]).
|
|||
### Top-level domains containing "kanidm"
|
||||
|
||||
We ask that you **do not** use the word `kanidm` as part of your instance's
|
||||
*top-level* (or [public suffix][ps]) domain, eg: `contosokanidm.example`.
|
||||
*top-level* (or [public-suffix-level][ps]) domain, eg: `contoso-kanidm.example`.
|
||||
|
||||
Use something like `auth`, `idm`, `login` or `sso` instead – they're shorter,
|
||||
too!
|
||||
|
@ -98,10 +99,10 @@ We're OK with you using `kanidm` in a *subdomain* to point to your Kanidm
|
|||
instance, eg: `kanidm.example.com`.
|
||||
|
||||
We've worked hard to build this project, and using its name in conjunction with
|
||||
an organisation *not* associated with the project dilutes the name's branding
|
||||
an organisation *not* associated with the project dilutes the name's brand
|
||||
value.
|
||||
|
||||
### Subdomains and Origin policy
|
||||
### Subdomains and Cross-Origin policy
|
||||
|
||||
Browsers allow a server on a subdomain to use intra-domain resources, and access
|
||||
and set credentials and cookies from all of its parents until a
|
||||
|
@ -154,9 +155,80 @@ This can be an issue if Kanidm shares a domain with:
|
|||
* third-party servers *outside* of your organisation's control (eg: SaaS apps)
|
||||
* anything which can be deployed to with minimal oversight (eg: a web host that
|
||||
allows uploading content via unencrypted FTP)
|
||||
* DNS entries that resolve to arbitrary IP addresses (eg:
|
||||
`192-0-2-1.ipv4.example.com` to `192.0.2.1`, and `192.0.2.1` is not under
|
||||
the authority of `example.com`)
|
||||
|
||||
[matrix-csp]: https://github.com/element-hq/synapse/blob/develop/README.rst#security-note
|
||||
|
||||
In most cases, hosting Kanidm on a subdomain of a separate top-level (or
|
||||
*existing* [public-suffix level][ps]) domain (eg: `idm.contoso-auth.example`) is
|
||||
sufficient to isolate your Kanidm deployment's `Origin` from other applications
|
||||
and services.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> There is generally **no need** to request additions to
|
||||
> [the public suffix list][ps] to deploy Kanidm securely,
|
||||
> *even for multi-environment deployments*.
|
||||
>
|
||||
> The **only** exception is to *remove* an *existing* opt-out that affects your
|
||||
> domain where it must operate under a particular suffix (eg: a NSW government
|
||||
> agency using `example.nsw.gov.au`).
|
||||
>
|
||||
> Such requests are a
|
||||
> [major burden for the *volunteer-operated* list][ps-diffusion], can take
|
||||
> [months to roll out to clients][ps-rollout], and changes may have unintended
|
||||
> side-effects.
|
||||
>
|
||||
> By comparison, registering a separate domain is easy, and takes minutes.
|
||||
|
||||
[ps-diffusion]: https://github.com/publicsuffix/list/wiki/Third-Party-Diffusion
|
||||
[ps-rollout]: https://github.com/publicsuffix/list/wiki/Guidelines#appropriate-expectations-on-derivative-propagation-use-or-inclusion
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Web apps (and APIs) that authenticate with
|
||||
> [OAuth 2.0/OpenID Connect](./integrations/oauth2.md) **never** need to share
|
||||
> cookies or Origin with Kanidm, so they **do not** need to be on the same
|
||||
> top-level (or [public-suffix-level][ps]) domain.
|
||||
>
|
||||
> Large public auth providers (eg: Google, Meta, Microsoft) work the same way
|
||||
> with both first and third-party web apps.
|
||||
|
||||
### Kanidm requires its own hostname
|
||||
|
||||
Kanidm must be the *only* thing running on a hostname, served from `/`, with all
|
||||
its paths served as-is.
|
||||
|
||||
It cannot:
|
||||
|
||||
* be run from a subdirectory (eg: `https://example.com/kanidm/`)
|
||||
* have *other* services accessible on the hostname in subdirectories (eg:
|
||||
`https://idm.example.com/wiki/`)
|
||||
* have *other* services accessible over HTTP or HTTPS at the same hostname on a
|
||||
different port (eg: `https://idm.example.com:8080/`)
|
||||
|
||||
These introduce similar security risks to the
|
||||
[subdomain issues described above](#subdomains-and-cross-origin-policy).
|
||||
|
||||
One reasonable exception is to serve [ACME HTTP-01 challenges][acme-http] (for
|
||||
Let's Encrypt) at `http://${hostname}/.well-known/acme-challenge/`. You'll need
|
||||
a *separate* HTTP server to respond to these challenges, and ensure that only
|
||||
authorised processes can request a certificate for Kanidm's hostname.
|
||||
|
||||
[acme-http]: https://letsencrypt.org/docs/challenge-types/#http-01-challenge
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> The `/.well-known/` path ([RFC 8615][]) can be assigned security-sensitive
|
||||
> meaning in other protocols, similar to [ACME HTTP-01][acme-http].
|
||||
>
|
||||
> Kanidm currently uses this path for OpenID Connect Discovery, and may use it
|
||||
> for other integrations in the future.
|
||||
|
||||
[RFC 8615]: https://datatracker.ietf.org/doc/html/rfc8615
|
||||
|
||||
### Avoid wildcard and widely-scoped certificates
|
||||
|
||||
CAs can issue wildcard TLS certificates, which apply to all subdomains in the
|
||||
|
@ -212,18 +284,12 @@ For **maximum** security, your Kanidm domain name should be a subdomain of a
|
|||
top-level domain (or domain under a [public suffix][ps]) that has no other
|
||||
services assigned it, eg:
|
||||
|
||||
* Origin: `https://idm.exampleauth.example`
|
||||
* Domain name: `idm.exampleauth.example`
|
||||
* Origin: `https://idm.example-auth.example`
|
||||
* Domain name: `idm.example-auth.example`
|
||||
|
||||
When using [OAuth 2.0/OpenID Connect](./integrations/oauth2.md), there is no
|
||||
need for a client app to share a top-level domain with Kanidm, because the app
|
||||
does not need to share cookies.
|
||||
|
||||
Large public auth providers (eg: Google, Meta, Microsoft) work the same way with
|
||||
both first and third-party apps.
|
||||
|
||||
If you have strict security controls for all apps on your top-level domain, you
|
||||
could run Kanidm on a subdomain of your main domain, eg:
|
||||
If you have
|
||||
[strict security controls for all apps on your top-level domain](#subdomains-and-cross-origin-policy),
|
||||
you could run Kanidm on a subdomain of your main domain, eg:
|
||||
|
||||
* Origin: `https://idm.example.com`
|
||||
* Domain name: `idm.example.com`
|
||||
|
@ -233,8 +299,9 @@ restrict changes that *could* affect your IDM infrastructure.
|
|||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This is the **inverse** of the common Active Directory practice of using the
|
||||
> organisation's primary top-level domain directly, eg: `example.com`.
|
||||
> Using a subdomain is the **inverse** of the common Active Directory practice
|
||||
> of using the organisation's primary top-level domain directly, eg:
|
||||
> `example.com`.
|
||||
|
||||
### Multi-environment and regional deployments
|
||||
|
||||
|
|
55
book/src/customising.md
Normal file
55
book/src/customising.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
# Customising
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Currently theming options such as updating the CSS requires modifying the style.css file. This
|
||||
> may be changed in the future to make it easier to modify.
|
||||
|
||||
Kanidm supports customising various aspects such as the site display name, site image, and display
|
||||
names and images for each application.
|
||||
|
||||
## Changing the site
|
||||
|
||||
### Updating the display Name
|
||||
|
||||
By default, the display name is 'Kanidm <hostname>' which is visible when logged in. To modify the
|
||||
display name, run the following
|
||||
|
||||
```bash
|
||||
kanidm system domain set-displayname <new-display-name> -D admin
|
||||
```
|
||||
|
||||
### Updating the site image
|
||||
|
||||
Similarly instead of the default Ferris the crab logo, the image on the signin page can be updated
|
||||
or reset with the below commands. The image must satisfy the following conditions:
|
||||
1. Maximum 1024 x 1024 pixels
|
||||
2. Less than 256 KB
|
||||
3. Is a supported image file type: png, jpg, gif, svg, webp
|
||||
|
||||
```bash
|
||||
kanidm system domain set-image <file-path> [image-type] -D admin
|
||||
|
||||
kanidm system domain remove-image -D admin
|
||||
```
|
||||
|
||||
## Changing a resource server
|
||||
|
||||
### Updating the display name
|
||||
|
||||
Each application can have its display name updated with the following
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 set-displayname <NAME> <displayname> -D idm_admin
|
||||
```
|
||||
|
||||
### Updating the image
|
||||
|
||||
Each application can have its image updated or reset with the following commands. The image is
|
||||
subject to the same restrictions as the site image above.
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 set-image <NAME> <file-path> [image-type] -D idm_admin
|
||||
|
||||
kanidm system oauth2 remove-image <NAME> -D idm_admin
|
||||
```
|
|
@ -7,10 +7,10 @@ support machine accounts also know as domain joining.
|
|||
|
||||
### Limiting Unix Password Auth
|
||||
|
||||
Currently unix password authentication is targetted as the method for sudo. Initial access to the
|
||||
Currently unix password authentication is targeted as the method for sudo. Initial access to the
|
||||
machine should come from ssh keys (and in future, ctap2).
|
||||
|
||||
In order to maintain compatability with LDAP style authentication, we allow "anonymous hosts" to
|
||||
In order to maintain compatibility with LDAP style authentication, we allow "anonymous hosts" to
|
||||
retrieve ssh public keys, and then perform sudo authentication.
|
||||
|
||||
This has the obvious caveat that anyone can stand up a machine that trusts a Kanidm instance. This
|
||||
|
|
|
@ -38,7 +38,7 @@ There are different ways we can scope a trust out, each with pros-cons. Here are
|
|||
may implement some controls around which subject DN's to allow/deny, but this is pretty fraught
|
||||
with landminds. You don't know who exists until they login!
|
||||
* Azure AD individual account trusting. Instead of trusting a whole domain you allow a user from
|
||||
a remote tennant to access your resources. You don't trust everyone in their tennant, just that
|
||||
a remote tenant to access your resources. You don't trust everyone in their tenant, just that
|
||||
one account that you can invite. You can then revoke them as needed.
|
||||
* Group-trust - FreeIPA does this with AD. It's still like kerberos, but you only trust a subset
|
||||
of the users determined by "groups" from the trusted site.
|
||||
|
|
|
@ -65,12 +65,12 @@ same. // TODO: Should a user be allowed to relabel their kanidm ssh keys ?
|
|||
|
||||
Due to their long length they should be line-wrapped into a text field so the entirety is visible
|
||||
when shown. To reduce visible clutter and inconsistent spacing we will put the values into
|
||||
collapsable elements.
|
||||
collapsible elements.
|
||||
|
||||
These collapsed elements must include:
|
||||
|
||||
- label
|
||||
- value's key type (ECDSA, rsa, ect..) and may include:
|
||||
- value's key type (ECDSA, rsa, etc..) and may include:
|
||||
- value's comment, truncated to some max length
|
||||
|
||||
#### Editing keys
|
||||
|
|
|
@ -54,7 +54,7 @@ to improve it as a result. This will necesitate a major rework of the project.
|
|||
|
||||
The current design treated the client as a trivial communication layer. The daemon/event loop
|
||||
contained all state including if the resolver was online or offline. Additionally the TPM and
|
||||
password caching operations primarily occured in the daemon layer, which limited the access of these
|
||||
password caching operations primarily occurred in the daemon layer, which limited the access of these
|
||||
features to the client backend itself.
|
||||
|
||||
### Future Features
|
||||
|
@ -130,7 +130,7 @@ future.
|
|||
#### CTAP2 / TPM-PIN
|
||||
|
||||
We want to allow local authentication with CTAP2 or a TPM with PIN. Both provide stronger assurances
|
||||
of both who the user is, and that they are in posession of a specific cryptographic device. The nice
|
||||
of both who the user is, and that they are in possession of a specific cryptographic device. The nice
|
||||
part of this is that they both implement hardware bruteforce protections. For soft-tpm we can
|
||||
emulate this with a strict bruteforce lockout prevention mechanism.
|
||||
|
||||
|
@ -384,7 +384,7 @@ and rely on sqlite heavily.
|
|||
We should migrate to a primarily in-memory cache, where sqlite is used only for persistence. The
|
||||
sqlite content should be optionally able to be encrypted by a TPM bound key.
|
||||
|
||||
To obsfucate details, the sqlite db should be a single table of key:value where keys are uuids
|
||||
To obfuscate details, the sqlite db should be a single table of key:value where keys are uuids
|
||||
associated to the item. The uuid is a local detail, not related to the provider.
|
||||
|
||||
The cache should move to a concread based concurrent tree which will also allow us to multi-thread
|
||||
|
|
|
@ -45,6 +45,7 @@ can take many forms such as.
|
|||
- firstname firstname lastname
|
||||
- firstname lastname lastname
|
||||
- firstname
|
||||
- middlename lastname
|
||||
- lastname firstname
|
||||
|
||||
And many many more that are not listed here. This is why our names are displayName as a freetext
|
||||
|
|
|
@ -114,3 +114,7 @@ When a service like sudo, sshd, su, etc. wants to authenticate someone, it opens
|
|||
that service, then performs authentication according to the modules defined in the pam.d config. For
|
||||
example, if you run `ls -al /etc/pam.d /usr/etc/pam.d` in SUSE, you can see the services and their
|
||||
respective pam.d config.
|
||||
|
||||
## Test coverage
|
||||
|
||||
We're trying to regularly get coverage reports into [Coveralls](https://coveralls.io/github/kanidm/kanidm), you can run the local testing with `make coverage` once you've installed [cargo-tarpaulin](https://crates.io/crates/cargo-tarpaulin).
|
||||
|
|
|
@ -100,21 +100,16 @@ You will need [rustup](https://rustup.rs/) to install a Rust toolchain.
|
|||
|
||||
### SUSE / OpenSUSE
|
||||
|
||||
> NOTE: clang and lld are required to build Kanidm due to performance issues with GCC/ld
|
||||
|
||||
You will need to install rustup and our build dependencies with:
|
||||
|
||||
```bash
|
||||
zypper in rustup git libudev-devel sqlite3-devel libopenssl-3-devel libselinux-devel pam-devel tpm2-0-tss-devel
|
||||
zypper in rustup git libudev-devel sqlite3-devel libopenssl-3-devel libselinux-devel \
|
||||
pam-devel systemd-devel tpm2-0-tss-devel clang lld make sccache
|
||||
```
|
||||
|
||||
You can then use rustup to complete the setup of the toolchain.
|
||||
|
||||
In some cases you may need to build other vendored components, or use an alternate linker. In these
|
||||
cases we advise you to also install.
|
||||
|
||||
```bash
|
||||
zypper in clang lld make sccache
|
||||
```
|
||||
|
||||
You should also adjust your environment with:
|
||||
|
||||
```bash
|
||||
|
@ -123,25 +118,16 @@ export CC="sccache /usr/bin/clang"
|
|||
export CXX="sccache /usr/bin/clang++"
|
||||
```
|
||||
|
||||
And add the following to a cargo config of your choice (such as ~/.cargo/config), adjusting for cpu
|
||||
arch
|
||||
|
||||
```toml
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
linker = "clang"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-fuse-ld=lld",
|
||||
]
|
||||
```
|
||||
|
||||
### Fedora
|
||||
|
||||
> NOTE: clang and lld are required to build Kanidm due to performance issues with GCC/ld
|
||||
|
||||
You will need [rustup](https://rustup.rs/) to install a Rust toolchain.
|
||||
|
||||
You will also need some system libraries to build this:
|
||||
|
||||
```text
|
||||
systemd-devel sqlite-devel openssl-devel pam-devel
|
||||
systemd-devel sqlite-devel openssl-devel pam-devel clang lld
|
||||
```
|
||||
|
||||
Building the Web UI requires additional packages:
|
||||
|
@ -152,12 +138,14 @@ perl-FindBin perl-File-Compare
|
|||
|
||||
### Ubuntu
|
||||
|
||||
> NOTE: clang and lld are required to build Kanidm due to performance issues with GCC/ld
|
||||
|
||||
You need [rustup](https://rustup.rs/) to install a Rust toolchain.
|
||||
|
||||
You will also need some system libraries to build this, which can be installed by running:
|
||||
|
||||
```bash
|
||||
sudo apt-get install libudev-dev libssl-dev pkg-config libpam0g-dev
|
||||
sudo apt-get install libudev-dev libssl-dev libsystemd-dev pkg-config libpam0g-dev clang lld
|
||||
```
|
||||
|
||||
Tested with Ubuntu 20.04 and 22.04.
|
||||
|
|
|
@ -3,57 +3,58 @@
|
|||
## Pre-Reqs
|
||||
|
||||
```bash
|
||||
cargo install cargo-audit
|
||||
cargo install cargo-outdated
|
||||
cargo install cargo-udeps
|
||||
cargo install cargo-machete
|
||||
cargo install --force \
|
||||
cargo-audit \
|
||||
cargo-outdated \
|
||||
cargo-udeps \
|
||||
cargo-machete
|
||||
```
|
||||
|
||||
## Pre Release Check List
|
||||
|
||||
### Start a release
|
||||
|
||||
- [ ] git checkout -b YYYYMMDD-pre-release
|
||||
- [ ] `git checkout -b "$(date +%Y%m%d)-pre-release"`
|
||||
|
||||
### Cargo Tasks
|
||||
|
||||
- [ ] Update MSRV if applicable
|
||||
- [ ] cargo update
|
||||
- [ ] `cargo update`
|
||||
- [ ] `RUSTC_BOOTSTRAP=1 cargo udeps`
|
||||
- [ ] `cargo machete`
|
||||
- [ ] cargo outdated -R
|
||||
- [ ] cargo audit
|
||||
- [ ] cargo test
|
||||
- [ ] `cargo machete --with-metadata`
|
||||
- [ ] `cargo outdated -R`
|
||||
- [ ] `cargo audit`
|
||||
- [ ] `cargo test`
|
||||
|
||||
- [ ] setup a local instance and run orca (TBD)
|
||||
- [ ] store a copy an an example db (TBD)
|
||||
|
||||
### Code Changes
|
||||
|
||||
- [ ] upgrade crypto policy values if required
|
||||
- [ ] upgrade crypto policy values if required (see `libs/crypto/src/lib.rs` -> `CryptoPolicy`)
|
||||
- [ ] check for breaking db entry changes.
|
||||
|
||||
### Administration
|
||||
|
||||
- [ ] Update `RELEASE_NOTES.md`
|
||||
- [ ] Update `README.md`
|
||||
- [ ] cargo test
|
||||
- [ ] git commit -a -m "Release Notes"
|
||||
- [ ] git push origin YYYYMMDD-pre-release
|
||||
- [ ] `cargo test`
|
||||
- [ ] `git commit -a -m 'chore: Release Notes'`
|
||||
- [ ] `git push origin "$(date +%Y%m%d)-pre-release"`
|
||||
- [ ] Merge PR
|
||||
|
||||
### Git Management
|
||||
|
||||
- [ ] git checkout master
|
||||
- [ ] git pull
|
||||
- [ ] `git checkout master`
|
||||
- [ ] `git pull`
|
||||
- [ ] git checkout -b 1.x.0 (Note no v to prevent ref conflict)
|
||||
- [ ] update version to set pre tag in ./Cargo.toml
|
||||
- [ ] git commit -m "Release 1.x.0-pre"
|
||||
- [ ] git tag v1.x.0-pre
|
||||
- [ ] `git commit -m "Release $(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')-pre"`
|
||||
- [ ] `git tag v$(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')-pre`
|
||||
|
||||
- [ ] Final inspect of the branch
|
||||
|
||||
- [ ] git push origin 1.x.0 --tags
|
||||
- [ ] `git push origin "$(cargo metadata --format-version 1 | jq '.packages[] | select(.name=="kanidm_proto") | .version')" --tags`
|
||||
|
||||
- [ ] github -> Ensure release branch is protected
|
||||
|
||||
|
@ -106,4 +107,3 @@ cargo install cargo-machete
|
|||
### Distro
|
||||
|
||||
- [ ] vendor and release to build.opensuse.org
|
||||
|
||||
|
|
|
@ -54,7 +54,6 @@ services:
|
|||
- traefik.http.routers.kanidm.entrypoints=websecure
|
||||
- traefik.http.routers.kanidm.rule=Host(`idm.example.com`)
|
||||
- traefik.http.routers.kanidm.service=kanidm
|
||||
- traefik.http.serversTransports.kanidm.insecureSkipVerify=true
|
||||
- traefik.http.services.kanidm.loadbalancer.server.port=8443
|
||||
- traefik.http.services.kanidm.loadbalancer.server.scheme=https
|
||||
volumes:
|
||||
|
|
|
@ -75,7 +75,7 @@ administrator. While they may not have direct access to the client/application s
|
|||
still use this `client_id+secret` to then carry out the authorisation code interception attack
|
||||
listed.
|
||||
|
||||
For confidential clients (refered to as a `basic` client in Kanidm due to the use of HTTP Basic for
|
||||
For confidential clients (referred to as a `basic` client in Kanidm due to the use of HTTP Basic for
|
||||
`client_id+secret` presentation) PKCE may optionally be disabled. This can allow authorisation code
|
||||
attacks to be carried out - however _if_ TLS is used and the `client_secret` never leaks, then these
|
||||
attacks will not be possible. Since there are many public references to system administrators
|
||||
|
|
|
@ -137,9 +137,9 @@ chmod 666 ~/.cache/kanidm_tokens
|
|||
docker pull kanidm/tools:latest
|
||||
docker run --rm -i -t \
|
||||
--network host \
|
||||
--mount "type=bind,src=/etc/kanidm/config,target=/etc/kanidm/config" \
|
||||
--mount "type=bind,src=$HOME/.config/kanidm,target=/home/kanidm/.config/kanidm" \
|
||||
--mount "type=bind,src=$HOME/.cache/kanidm_tokens,target=/home/kanidm/.cache/kanidm_tokens" \
|
||||
--mount "type=bind,src=/etc/kanidm/config,target=/data/config:ro" \
|
||||
--mount "type=bind,src=$HOME/.config/kanidm,target=/root/.config/kanidm" \
|
||||
--mount "type=bind,src=$HOME/.cache/kanidm_tokens,target=/root/.cache/kanidm_tokens" \
|
||||
kanidm/tools:latest \
|
||||
/sbin/kanidm --help
|
||||
```
|
||||
|
|
|
@ -145,7 +145,8 @@ with a dn of `dn=token` and provide the api token in the password.
|
|||
> [!NOTE]
|
||||
>
|
||||
> The `dn=token` keyword is guaranteed to not be used by any other entry, which is why it was chosen
|
||||
> as the keyword to initiate api token binds.
|
||||
> as the keyword to initiate api token binds. Additionally it is not required, leaving the field empty
|
||||
> will fall back to the service-account if a "password" is provided
|
||||
|
||||
```bash
|
||||
ldapwhoami -H ldaps://URL -x -D "dn=token" -w "TOKEN"
|
||||
|
@ -234,6 +235,7 @@ ldapwhoami ... -x -D '22a65b6c-80c8-4e1a-9b76-3f3afdff8400'
|
|||
ldapwhoami ... -x -D 'spn=test1@idm.example.com,dc=idm,dc=example,dc=com'
|
||||
ldapwhoami ... -x -D 'name=test1,dc=idm,dc=example,dc=com'
|
||||
```
|
||||
<sub>in fact, the key of the bind isn't used at all so `googoogaaga=test1` is entirely valid</sub> ;)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ introspection.
|
|||
|
||||
Kanidm will expose its OAuth2 APIs at the following URLs, substituting
|
||||
`:client_id:` with an OAuth2 client ID.
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
<dl>
|
||||
|
||||
|
@ -59,7 +60,7 @@ URL **(recommended)**
|
|||
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/openid-configuration`
|
||||
|
||||
This document includes all the URLs and attributes an app needs to be able to
|
||||
authenticate using OIDC with Kanidm, *except* for the `client_id` and
|
||||
authenticate using OIDC with Kanidm, _except_ for the `client_id` and
|
||||
`client_secret`.
|
||||
|
||||
Use this document wherever possible, as it will allow you to easily build and/or
|
||||
|
@ -69,11 +70,10 @@ anything special for Kanidm (or another provider).
|
|||
**Note:** some apps automatically append `/.well-known/openid-configuration` to
|
||||
the end of an OIDC Discovery URL, so you may need to omit that.
|
||||
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
|
||||
[RFC 8414 OAuth 2.0 Authorisation Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) URL
|
||||
[RFC 8414 OAuth 2.0 Authorisation Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414)
|
||||
URL **(recommended)**
|
||||
|
||||
</dt>
|
||||
|
||||
|
@ -85,6 +85,21 @@ the end of an OIDC Discovery URL, so you may need to omit that.
|
|||
|
||||
<dt>
|
||||
|
||||
[WebFinger URL](#webfinger) **(discouraged)**
|
||||
|
||||
</dt>
|
||||
|
||||
<dd>
|
||||
|
||||
`https://idm.example.com/oauth2/openid/:client_id:/.well-known/webfinger`
|
||||
|
||||
See [the WebFinger section](#webfinger) for more details, as there a number of
|
||||
caveats for WebFinger clients.
|
||||
|
||||
</dd>
|
||||
|
||||
<dt>
|
||||
|
||||
User auth
|
||||
|
||||
</dt>
|
||||
|
@ -147,7 +162,7 @@ Token endpoint
|
|||
|
||||
<dt>
|
||||
|
||||
OpenID Connect issuer URI
|
||||
OpenID Connect Issuer URL
|
||||
|
||||
</dt>
|
||||
|
||||
|
@ -183,11 +198,13 @@ Token signing public key
|
|||
|
||||
</dl>
|
||||
|
||||
<!-- markdownlint-enable MD037 -->
|
||||
|
||||
## Configuration
|
||||
|
||||
### Create the Kanidm Configuration
|
||||
|
||||
By default, members of the `system_admins` or `idm_hp_oauth2_manage_priv` groups are able to create
|
||||
By default, members of the `idm_admins` or `idm_oauth2_admins` groups are able to create
|
||||
or manage OAuth2 client integrations.
|
||||
|
||||
You can create a new client by specifying its client name, application display name and the landing
|
||||
|
@ -210,7 +227,7 @@ You can create a scope map with:
|
|||
|
||||
```bash
|
||||
kanidm system oauth2 update-scope-map <name> <kanidm_group_name> [scopes]...
|
||||
kanidm system oauth2 update-scope-map nextcloud nextcloud_admins admin
|
||||
kanidm system oauth2 update-scope-map nextcloud nextcloud_users email profile openid
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
|
@ -220,18 +237,27 @@ kanidm system oauth2 update-scope-map nextcloud nextcloud_admins admin
|
|||
> claims may be added to the authorisation token. It is not guaranteed that all of the associated
|
||||
> claims will be added.
|
||||
>
|
||||
> - **profile** - name, family_name, given_name, middle_name, nickname, preferred_username, profile,
|
||||
> * **profile** - name, family_name, given_name, middle_name, nickname, preferred_username, profile,
|
||||
> picture, website, gender, birthdate, zoneinfo, locale, and updated_at
|
||||
> - **email** - email, email_verified
|
||||
> - **address** - address
|
||||
> - **phone** - phone_number, phone_number_verified
|
||||
> * **email** - email, email_verified
|
||||
> * **address** - address
|
||||
> * **phone** - phone_number, phone_number_verified
|
||||
> * **groups** - groups
|
||||
>
|
||||
> In addition Kanidm supports some vendor specific scopes that can include additional claims.
|
||||
>
|
||||
> * **ssh_publickeys** - array of ssh_publickey of the user
|
||||
|
||||
<!-- this is just to split the templates up -->
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> If you are creating an OpenID Connect (OIDC) client you **MUST** provide a scope map named
|
||||
> If you are creating an OpenID Connect (OIDC) client you **MUST** provide a scope map containing
|
||||
> `openid`. Without this, OpenID Connect clients **WILL NOT WORK**!
|
||||
>
|
||||
> ```bash
|
||||
> kanidm system oauth2 update-scope-map nextcloud nextcloud_users openid
|
||||
> ```
|
||||
|
||||
You can create a supplemental scope map with:
|
||||
|
||||
|
@ -311,10 +337,7 @@ applications that act as the OAuth2 client and its corresponding webserver is th
|
|||
In this case, the SPA is unable to act as a confidential client since the basic secret would need to
|
||||
be embedded in every client.
|
||||
|
||||
Another common example is native applications that use a redirect to localhost. These can't have a
|
||||
client secret embedded, so must act as public clients.
|
||||
|
||||
Public clients for this reason require PKCE to bind a specific browser session to its OAuth2
|
||||
For this reason, public clients require PKCE to bind a specific browser session to its OAuth2
|
||||
exchange. PKCE can not be disabled for public clients for this reason.
|
||||
|
||||
To create an OAuth2 public client:
|
||||
|
@ -324,7 +347,13 @@ kanidm system oauth2 create-public <name> <displayname> <origin>
|
|||
kanidm system oauth2 create-public mywebapp "My Web App" https://webapp.example.com
|
||||
```
|
||||
|
||||
To allow localhost redirection
|
||||
## Native Applications
|
||||
|
||||
Some applications will run a local web server on the user's device which directs users to the IDP for
|
||||
authentication, then back to the local server. [BCP212](https://www.rfc-editor.org/info/bcp212)
|
||||
"OAuth 2.0 for Native Apps" specifies the rules for this.
|
||||
|
||||
First allow localhost redirects:
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 enable-localhost-redirects <name>
|
||||
|
@ -332,6 +361,10 @@ kanidm system oauth2 disable-localhost-redirects <name>
|
|||
kanidm system oauth2 enable-localhost-redirects mywebapp
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Kanidm only allows these to be enabled on public clients where PKCE is enforced.
|
||||
|
||||
## Alternate Redirect URLs
|
||||
|
||||
> [!WARNING]
|
||||
|
@ -374,8 +407,10 @@ To indicate your readiness for this transition, all OAuth2 clients must have the
|
|||
`strict-redirect-url` enabled. Once enabled, the client will begin to enforce the 1.4.0 strict
|
||||
validation behaviour.
|
||||
|
||||
If you have not enabled `strict-redirect-url` on all OAuth2 clients the upgrade to 1.4.0 will refuse
|
||||
to proceed.
|
||||
> [!WARNING]
|
||||
>
|
||||
> If you have not enabled `strict-redirect-url` on all OAuth2 clients the upgrade to 1.4.0 will refuse
|
||||
> to proceed.
|
||||
|
||||
To enable or disable strict validation:
|
||||
|
||||
|
@ -420,3 +455,86 @@ kanidm system oauth2 reset-secrets
|
|||
```
|
||||
|
||||
Each client has unique signing keys and access secrets, so this is limited to each service.
|
||||
|
||||
## WebFinger
|
||||
|
||||
[WebFinger][webfinger] provides a mechanism for discovering information about
|
||||
entities at a well-known URL (`https://{hostname}/.well-known/webfinger`).
|
||||
|
||||
It can be used by a WebFinger client to
|
||||
[discover the OIDC Issuer URL][webfinger-oidc] of an identity provider from the
|
||||
hostname alone, and seems to be intended to support dynamic client registration
|
||||
flows for large public identity providers.
|
||||
|
||||
Kanidm v1.5.1 and later can respond to WebFinger requests, using a user's SPN as
|
||||
part of [an `acct` URI][rfc7565] (eg: `acct:user@idm.example.com`). While SPNs
|
||||
and `acct` URIs look like email addresses, [as per RFC 7565][rfc7565s4], there
|
||||
is no guarantee that it is valid for any particular application protocol, unless
|
||||
an administrator explicitly provides for it.
|
||||
|
||||
When setting up an application to authenticate with Kanidm, WebFinger **does not
|
||||
add any security** over configuring an OIDC Discovery URL directly. In an OIDC
|
||||
context, the specification makes a number of flawed assumptions which make it
|
||||
difficult to use with Kanidm:
|
||||
|
||||
* WebFinger assumes that an identity provider will use the same Issuer URL and
|
||||
OIDC Discovery document (which contains endpoint URLs and token signing keys)
|
||||
for *all* OAuth 2.0/OIDC clients.
|
||||
|
||||
Kanidm uses *client-specific* Issuer URLs, endpoint URLs and token signing
|
||||
keys. This ensures that tokens can only be used with their intended service.
|
||||
|
||||
* WebFinger endpoints must be served at the *root* of the domain of a user's
|
||||
SPN (ie: information about the user with SPN `user@idm.example.com` is at
|
||||
`https://idm.example.com/.well-known/webfinger?resource=acct%3Auser%40idm.example.com`).
|
||||
|
||||
Unlike OIDC Discovery, WebFinger clients do not report their OAuth 2.0/OIDC
|
||||
client ID in the request, so there is no way to tell them apart.
|
||||
|
||||
As a result, Kanidm *does not* provide a WebFinger endpoint at its root URL,
|
||||
because it could report an incorrect Issuer URL and lead the client to an
|
||||
incorrect OIDC Discovery document.
|
||||
|
||||
You will need a load balancer in front of Kanidm's HTTPS server to send a HTTP
|
||||
307 redirect to the appropriate
|
||||
`/oauth2/openid/:client_id:/.well-known/webfinger` URL, *while preserving all
|
||||
query parameters*. For example, with Caddy:
|
||||
|
||||
```caddy
|
||||
# Match on a prefix, and use {uri} to preserve all query parameters.
|
||||
# This only supports *one* client.
|
||||
example.com {
|
||||
redir /.well-known/webfinger https://idm.example.com/oauth2/openid/:client_id:{uri} 307
|
||||
}
|
||||
```
|
||||
|
||||
If you have *multiple* WebFinger clients, it will need to map some other
|
||||
property of the request (such as a source IP address or `User-Agent` header)
|
||||
to a client ID, and redirect to the appropriate WebFinger URL for that client.
|
||||
|
||||
* Kanidm responds to *all* WebFinger queries with
|
||||
[an Identity Provider Discovery for OIDC URL][webfinger-oidc], **ignoring**
|
||||
[`rel` parameter(s)][webfinger-rel].
|
||||
|
||||
If you want to use WebFinger in any *other* context on Kanidm's hostname,
|
||||
you'll need a load balancer in front of Kanidm which matches on some property
|
||||
of the request.
|
||||
|
||||
WebFinger clients *may* omit the `rel=` parameter, so if you host another
|
||||
service with relations for a Kanidm [`acct:` entity][rfc7565s4] and a client
|
||||
*does not* supply the `rel=` parameter, your load balancer will need to merge
|
||||
JSON responses from Kanidm and the other service(s).
|
||||
|
||||
Because of these issues, we recommend that applications support *directly*
|
||||
configuring OIDC using a Discovery URL or OAuth 2.0 Authorisation Server
|
||||
Metadata URL instead of WebFinger.
|
||||
|
||||
If a WebFinger client only checks WebFinger once during setup, you may wish to
|
||||
temporarily serve an appropriate static WebFinger document for that client
|
||||
instead.
|
||||
|
||||
[rfc7565]: https://datatracker.ietf.org/doc/html/rfc7565
|
||||
[rfc7565s4]: https://datatracker.ietf.org/doc/html/rfc7565#section-4
|
||||
[webfinger]: https://datatracker.ietf.org/doc/html/rfc7033
|
||||
[webfinger-oidc]: https://datatracker.ietf.org/doc/html/rfc7033#section-3.1
|
||||
[webfinger-rel]: https://datatracker.ietf.org/doc/html/rfc7033#section-4.3
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# Example OAuth2 Configurations
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Web applications that authenticate with Kanidm **must** be served over HTTPS.
|
||||
|
||||
## Apache `mod_auth_openidc`
|
||||
|
||||
Add the following to a `mod_auth_openidc.conf`. It should be included in a `mods_enabled` folder or
|
||||
|
@ -50,6 +54,99 @@ In the virtual host, to protect a location/directory
|
|||
</Directory>
|
||||
```
|
||||
|
||||
## Gitea
|
||||
|
||||
[Gitea](https://docs.gitea.com/) is a painless, self-hosted, all-in-one software
|
||||
development service. It has built in support for
|
||||
[external authentication](https://docs.gitea.com/administration/authentication)
|
||||
including OAuth2.
|
||||
|
||||
To set up a Gitea instance to authenticate with Kanidm:
|
||||
|
||||
1. Add an email address to your regular Kanidm account, if it doesn't have one
|
||||
already:
|
||||
|
||||
```sh
|
||||
kanidm person update your_username -m your_username@example.com
|
||||
```
|
||||
|
||||
2. Create a new Kanidm group for your Gitea users (`gitea_users`), and add your
|
||||
regular account to it:
|
||||
|
||||
```sh
|
||||
kanidm group create gitea_users
|
||||
kanidm group add-members gitea_users your_username
|
||||
```
|
||||
|
||||
3. Create a new OAuth2 application configuration in Kanidm (`gitea`), configure
|
||||
the redirect URL, and scope access to the `gitea_users` group:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 create gitea Gitea https://gitea.example.com/user/login
|
||||
kanidm system oauth2 add-redirect-url gitea https://gitea.example.com/user/oauth2/kanidm/callback
|
||||
kanidm system oauth2 update-scope-map gitea gitea_users email openid profile groups
|
||||
```
|
||||
|
||||
4. Gitea currently [does not support PKCE](https://github.com/go-gitea/gitea/issues/21376)
|
||||
in their OIDC implementation. If you do not perform this step, you will see an error like
|
||||
`No PKCE code challenge was provided with client in enforced PKCE mode.`
|
||||
in your Kanidm server logs. Therefore, we have to disable PKCE for Gitea:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 warning-insecure-client-disable-pkce gitea
|
||||
```
|
||||
|
||||
5. Get the `gitea` OAuth2 client secret from Kanidm:
|
||||
|
||||
```sh
|
||||
kanidm system oauth2 show-basic-secret gitea
|
||||
```
|
||||
|
||||
6. Log in to Gitea with an administrator account and go to Site Administration
|
||||
-> Identity & Access -> Authentication Sources, and "Add Authentication Source",
|
||||
then provide the following details:
|
||||
* **Type**: `OAuth2`
|
||||
* **Name**: `kanidm`, in case you want to choose a different name, make sure
|
||||
to update `kanidm` in the redirect URL in step 3. The full redirect URL is
|
||||
provided at the bottom of the current configuration page in Gitea.
|
||||
* **OAuth2 Provider**: `OpenID Connect`
|
||||
* **Client ID (key)**: `gitea`
|
||||
* **Client Secret**: [from show-basic-secret above]
|
||||
* **OpenID Connect Auto Discovery URL**: `https://kanidm.example.com/oauth2/openid/gitea/.well-known/openid-configuration`
|
||||
|
||||
Alternatively, you can provide the configuration via the CLI:
|
||||
|
||||
```sh
|
||||
gitea admin auth add-oauth \
|
||||
--provider=openidConnect \
|
||||
--name=kanidm \
|
||||
--key=gitea \
|
||||
--secret=[from show-basic-secret above] \
|
||||
--auto-discover-url=https://kanidm.example.com/oauth2/openid/gitea/.well-known/openid-configuration \
|
||||
```
|
||||
|
||||
You should now see a "Sign in with Kanidm" button on your Gitea login page.
|
||||
|
||||
You may additionally want to configure:
|
||||
|
||||
* A Gitea themed icon in Kanidm for the `gitea` OAuth2 application:
|
||||
```sh
|
||||
curl -LO https://gitea.example.com/assets/img/logo.svg
|
||||
kanidm system oauth2 set-image gitea logo.svg svg
|
||||
rm logo.svg
|
||||
```
|
||||
|
||||
* To disable password authentication in Gitea, add the following
|
||||
[configuration](https://docs.gitea.com/next/administration/config-cheat-sheet)
|
||||
to `app.ini`:
|
||||
|
||||
```ini
|
||||
[service]
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true
|
||||
SHOW_REGISTRATION_BUTTON = false
|
||||
ENABLE_PASSWORD_SIGNIN_FORM = false
|
||||
```
|
||||
|
||||
## GitLab
|
||||
|
||||
[GitLab](https://gitlab.com) is a Git-based software development platform, which
|
||||
|
@ -261,7 +358,7 @@ using OAuth2:
|
|||
<dd>
|
||||
|
||||
Upload a Kanidm or other organisational logo.
|
||||
|
||||
|
||||
This will appear on the login form (with no text) to prompt users to sign
|
||||
in.
|
||||
|
||||
|
@ -459,6 +556,65 @@ php occ config:app:set --value=0 user_oidc allow_multiple_user_backends
|
|||
You can login directly by appending `?direct=1` to your login page. You can re-enable other backends
|
||||
by setting the value to `1`
|
||||
|
||||
## OAuth2 Proxy
|
||||
|
||||
OAuth2 Proxy is a reverse proxy that provides authentication with OpenID Connect identity providers.
|
||||
It is typically used to secure web applications without native OpenID Connect support.
|
||||
|
||||
Prepare the environment.
|
||||
Due to a [lack of public client support](https://github.com/oauth2-proxy/oauth2-proxy/issues/1714) we have to set it up as a basic client.
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 create webapp 'webapp.example.com' 'https://webapp.example.com'
|
||||
kanidm system oauth2 add-redirect-url webapp 'https://webapp.example.com/oauth2/callback'
|
||||
kanidm system oauth2 update-scope-map webapp email openid
|
||||
kanidm system oauth2 get webapp
|
||||
kanidm system oauth2 show-basic-secret webapp
|
||||
<SECRET>
|
||||
```
|
||||
|
||||
Create a user group.
|
||||
|
||||
```bash
|
||||
kanidm group create 'webapp_admin'
|
||||
```
|
||||
|
||||
Setup the claim-map to add `webapp_group` to the userinfo claim.
|
||||
|
||||
```bash
|
||||
kanidm system oauth2 update-claim-map-join 'webapp' 'webapp_group' array
|
||||
kanidm system oauth2 update-claim-map 'webapp' 'webapp_group' 'webapp_admin' 'webapp_admin'
|
||||
```
|
||||
|
||||
Authorize users for the application.
|
||||
Additionally OAuth2 Proxy requires all users have an email, reference this issue for more details:
|
||||
|
||||
- <https://github.com/oauth2-proxy/oauth2-proxy/issues/2667>
|
||||
|
||||
```bash
|
||||
kanidm person update '<user>' --legalname 'Personal Name' --mail 'user@example.com'
|
||||
kanidm group add-members 'webapp_admin' '<user>'
|
||||
```
|
||||
|
||||
And add the following to your OAuth2 Proxy config.
|
||||
|
||||
```toml
|
||||
provider = "oidc"
|
||||
scope = "openid email"
|
||||
# change to match your kanidm domain and client id
|
||||
oidc_issuer_url = "https://idm.example.com/oauth2/openid/webapp"
|
||||
# client ID from `kanidm system oauth2 create`
|
||||
client_id = "webapp"
|
||||
# redirect URL from `kanidm system add-redirect-url webapp`
|
||||
redirect_url = "https://webapp.example.com/oauth2/callback"
|
||||
# claim name from `kanidm system oauth2 update-claim-map-join`
|
||||
oidc_groups_claim = "webapp_group"
|
||||
# user group from `kanidm group create`
|
||||
allowed_groups = ["webapp_admin"]
|
||||
# secret from `kanidm system oauth2 show-basic-secret webapp`
|
||||
client_secret = "<SECRET>"
|
||||
```
|
||||
|
||||
## Outline
|
||||
|
||||
> These instructions were tested with self-hosted Outline 0.80.2.
|
||||
|
@ -480,7 +636,7 @@ with some limitations:
|
|||
|
||||
It will set the user's preferred name on *first* log in *only*.
|
||||
|
||||
To set up a *new* self-hosted Outline instance to authenicate with Kanidm:
|
||||
To set up a *new* self-hosted Outline instance to authenticate with Kanidm:
|
||||
|
||||
1. Add an email address to your regular Kanidm account, if it doesn't have one
|
||||
already:
|
||||
|
@ -651,7 +807,22 @@ To set up an ownCloud instance to authenticate with Kanidm:
|
|||
kanidm system oauth2 show-basic-secret owncloud
|
||||
```
|
||||
|
||||
7. Create a JSON configuration file (`oidc-config.json`) for ownCloud's OIDC
|
||||
7. Set [ownCloud's session cookie `SameSite` value to `Lax`][owncloud-samesite]:
|
||||
|
||||
* For manual installations, add the option
|
||||
`'http.cookie.samesite' => 'Lax',` to `config.php`.
|
||||
* For Docker installations, set the `OWNCLOUD_HTTP_COOKIE_SAMESITE`
|
||||
environment variable to `Lax`, then stop and start the container.
|
||||
|
||||
When ownCloud and Kanidm are on different top-level domains
|
||||
([as we recommend](../../choosing_a_domain_name.md#subdomains-and-cross-origin-policy)),
|
||||
ownCloud's default `SameSite=Strict` session cookie policy causes browsers
|
||||
to drop the session cookie when Kanidm redirects back to ownCloud, which
|
||||
then causes their OIDC library to
|
||||
[send an invalid token request to Kanidm][owncloud-session-bug], which
|
||||
Kanidm (correctly) rejects.
|
||||
|
||||
8. Create a JSON configuration file (`oidc-config.json`) for ownCloud's OIDC
|
||||
App.
|
||||
|
||||
To key users by UID (most secure configuration, but not suitable if you have
|
||||
|
@ -687,7 +858,7 @@ To set up an ownCloud instance to authenticate with Kanidm:
|
|||
}
|
||||
```
|
||||
|
||||
8. Deploy the config file you created with [`occ`][occ].
|
||||
9. Deploy the config file you created with [`occ`][occ].
|
||||
|
||||
[The exact command varies][occ] depending on how you've deployed ownCloud.
|
||||
|
||||
|
@ -726,7 +897,9 @@ login form, which you can use to sign in.
|
|||
|
||||
[owncloud-branding]: https://doc.owncloud.com/server/next/admin_manual/enterprise/clients/creating_branded_apps.html
|
||||
[owncloud-oidcsd]: https://doc.owncloud.com/server/next/admin_manual/configuration/user/oidc/oidc.html#set-up-service-discovery
|
||||
[owncloud-samesite]: https://doc.owncloud.com/server/next/admin_manual/configuration/server/config_sample_php_parameters.html#define-how-to-relax-same-site-cookie-settings
|
||||
[owncloud-secrets]: https://doc.owncloud.com/server/next/admin_manual/configuration/user/oidc/oidc.html#client-ids-secrets-and-redirect-uris
|
||||
[owncloud-session-bug]: https://github.com/jumbojett/OpenID-Connect-PHP/issues/453
|
||||
[owncloud-oauth2-app]: https://marketplace.owncloud.com/apps/oauth2
|
||||
[owncloud-ios-mdm]: https://doc.owncloud.com/ios-app/12.2/appendices/mdm.html#oauth2-based-authentication
|
||||
[occ]: https://doc.owncloud.com/server/next/admin_manual/configuration/server/occ_command.html
|
||||
|
@ -778,6 +951,7 @@ Prepare the environment:
|
|||
|
||||
```bash
|
||||
kanidm system oauth2 create grafana "grafana.domain.name" https://grafana.domain.name
|
||||
kanidm system oauth2 set-landing-url grafana 'https://grafana.domain.name/login/generic_oauth'
|
||||
kanidm system oauth2 update-scope-map grafana grafana_users email openid profile groups
|
||||
kanidm system oauth2 enable-pkce grafana
|
||||
kanidm system oauth2 get grafana
|
||||
|
|
|
@ -97,88 +97,32 @@ kanidm group add-members --name admin idm_radius_servers radius_service_account
|
|||
Now reset the account password, using the `admin` account:
|
||||
|
||||
```bash
|
||||
kanidm service-account credential generate --name admin radius_service_account
|
||||
kanidm service-account api-token generate --name admin radius_service_account
|
||||
```
|
||||
|
||||
## Deploying a RADIUS Container
|
||||
|
||||
We provide a RADIUS container that has all the needed integrations. This container requires some
|
||||
cryptographic material, with the following files being in `/etc/raddb/certs`. (Modifiable in the
|
||||
cryptographic material, with the following files mounted in `/data`. (Modifiable in the
|
||||
configuration)
|
||||
|
||||
| filename | description |
|
||||
| -------- | ------------------------------------------------------------- |
|
||||
| ca.pem | The signing CA of the RADIUS certificate |
|
||||
| dh.pem | The output of `openssl dhparam -in ca.pem -out ./dh.pem 2048` |
|
||||
| cert.pem | The certificate for the RADIUS server |
|
||||
| key.pem | The signing key for the RADIUS certificate |
|
||||
| filename | description |
|
||||
| -------- | ------------------------------------------------------------- |
|
||||
| ca.pem | The signing CA of the RADIUS certificate |
|
||||
| cert.pem | The certificate for the RADIUS server |
|
||||
| key.pem | The private key for the RADIUS certificate |
|
||||
| radius.toml | The configuration file |
|
||||
|
||||
The configuration file (`/data/kanidm`) has the following template:
|
||||
The configuration file (which you should mount at `/data/radius.toml`, or specify its path with the environment variable `KANIDM_RLM_CONFIG`) has the following template:
|
||||
|
||||
```toml
|
||||
uri = "https://example.com" # URL to the Kanidm server
|
||||
verify_hostnames = true # verify the hostname of the Kanidm server
|
||||
|
||||
verify_ca = false # Strict CA verification
|
||||
ca = /data/ca.pem # Path to the kanidm ca
|
||||
|
||||
auth_token = "ABC..." # Auth token for the service account
|
||||
# See: kanidm service-account api-token generate
|
||||
|
||||
# Default vlans for groups that don't specify one.
|
||||
radius_default_vlan = 1
|
||||
|
||||
# A list of Kanidm groups which must be a member
|
||||
# before they can authenticate via RADIUS.
|
||||
radius_required_groups = [
|
||||
"radius_access_allowed@idm.example.com",
|
||||
]
|
||||
|
||||
# A mapping between Kanidm groups and VLANS
|
||||
radius_groups = [
|
||||
{ spn = "radius_access_allowed@idm.example.com", vlan = 10 },
|
||||
]
|
||||
|
||||
# A mapping of clients and their authentication tokens
|
||||
radius_clients = [
|
||||
{ name = "test", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker" , ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
|
||||
# radius_cert_path = "/etc/raddb/certs/cert.pem"
|
||||
# the signing key for radius TLS
|
||||
# radius_key_path = "/etc/raddb/certs/key.pem"
|
||||
# the diffie-hellman output
|
||||
# radius_dh_path = "/etc/raddb/certs/dh.pem"
|
||||
# the CA certificate
|
||||
# radius_ca_path = "/etc/raddb/certs/ca.pem"
|
||||
{{#rustdoc_include ../../../examples/radius.toml}}
|
||||
```
|
||||
|
||||
## A fully configured example
|
||||
|
||||
```toml
|
||||
url = "https://example.com"
|
||||
|
||||
# The auth token for the service account
|
||||
auth_token = "ABC..."
|
||||
|
||||
# default vlan for groups that don't specify one.
|
||||
radius_default_vlan = 99
|
||||
|
||||
# if the user is in one of these Kanidm groups,
|
||||
# then they're allowed to authenticate
|
||||
radius_required_groups = [
|
||||
"radius_access_allowed@idm.example.com",
|
||||
]
|
||||
|
||||
radius_groups = [
|
||||
{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }
|
||||
]
|
||||
|
||||
radius_clients = [
|
||||
{ name = "localhost", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker" , ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
{{#rustdoc_include ../../../examples/radius_full.toml}}
|
||||
```
|
||||
|
||||
## Moving to Production
|
||||
|
@ -200,14 +144,17 @@ the problem. To increase the logging level you can re-run your environment with
|
|||
```bash
|
||||
docker rm radiusd
|
||||
docker run --name radiusd \
|
||||
-e DEBUG=True \
|
||||
--rm -e DEBUG=True \
|
||||
-p 1812:1812 \
|
||||
-p 1812:1812/udp
|
||||
-p 1812:1812/udp \
|
||||
--interactive --tty \
|
||||
--volume /tmp/kanidm:/etc/raddb/certs \
|
||||
--mount "type=bind,src=$(pwd)/examples/radius.toml,target=/data/kanidm" \
|
||||
--mount "type=bind,src=/tmp/kanidm,target=/data" \
|
||||
kanidm/radius:latest
|
||||
```
|
||||
|
||||
In this example we're running it from the root of the repository and loading an example config, and using the certificates generated in dev-mode. You'll need to adjust your mounts to suit!
|
||||
|
||||
Note: the RADIUS container _is_ configured to provide
|
||||
[Tunnel-Private-Group-ID](https://freeradius.org/rfc/rfc2868.html#Tunnel-Private-Group-ID), so if
|
||||
you wish to use Wi-Fi-assigned VLANs on your infrastructure, you can assign these by groups in the
|
||||
|
|
|
@ -5,57 +5,45 @@
|
|||
- Debian packaging is complex enough that it lives in a separate repository:
|
||||
[kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation).
|
||||
- While official packages are available at https://kanidm.github.io/kanidm_ppa/ these instructions will guide you
|
||||
through replicating the same process locally, using [cross](https://github.com/cross-rs/cross) & Docker to isolate the build process
|
||||
from your normal computer and allow building packages for multiple architectures.
|
||||
through replicating the same process locally, using Docker to isolate the build process from your normal computer.
|
||||
- Due to the complexity of crosscompilation, we no longer support it and recommend building natively,
|
||||
i.e. on the platform you're targeting.
|
||||
- While the examples below will use `aarch64-unknown-linux-gnu` aka `arm64`,
|
||||
the same process works for `x86_64-unknown-linux-gnu` aka `amd64` as well.
|
||||
|
||||
1. Start in the root directory of the main [kanidm/kanidm](https://github.com/kanidm/kanidm) repository.
|
||||
1. Install cross:
|
||||
```shell
|
||||
cargo install cross
|
||||
```
|
||||
1. Pull in the separate deb packaging submodule:
|
||||
```shell
|
||||
git submodule update platform/debian/kanidm_ppa_automation
|
||||
```
|
||||
1. Launch your desired crossbuild target. Do note the script assumes you use rustup!
|
||||
```shell
|
||||
# See valid targets:
|
||||
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh
|
||||
# Launch a target:
|
||||
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh debian-12-aarch64-unknown-linux-gnu
|
||||
# You can also specify multiple targets within the same distribution:
|
||||
platform/debian/kanidm_ppa_automation/scripts/crossbuild.sh debian-12-{aarch64,x86_64}-unknown-linux-gnu
|
||||
```
|
||||
1. Go get a drink of your choice while the build completes.
|
||||
1. Create a sacrificial deb builder container to avoid changing your own system:
|
||||
```shell
|
||||
docker run --rm -it -e CI=true \
|
||||
docker run --rm -it -e VERBOSE=true -e CI=true \
|
||||
--mount "type=bind,src=$PWD,target=/src" \
|
||||
--workdir /src \
|
||||
rust:bookworm
|
||||
```
|
||||
1. In the container install dependencies with:
|
||||
```shell
|
||||
# The parameter given is which additional target debian architecture to enable (amd64, arm64, etc.)
|
||||
# If your native platform is amd64, running with arm64 is enough to cover both archs.
|
||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh arm64
|
||||
platform/debian/kanidm_ppa_automation/scripts/install_ci_build_dependencies.sh
|
||||
```
|
||||
1. In the container launch the deb build:
|
||||
1. Launch your desired target build:
|
||||
```shell
|
||||
platform/debian/kanidm_ppa_automation/scripts/build_native.sh aarch64-unknown-linux-gnu
|
||||
```
|
||||
1. Go get a drink of your choice while the build completes.
|
||||
1. Launch the deb build:
|
||||
```shell
|
||||
platform/debian/kanidm_ppa_automation/scripts/build_debs.sh aarch64-unknown-linux-gnu
|
||||
# Again, multiple targets also work:
|
||||
platform/debian/kanidm_ppa_automation/scripts/build_debs.sh {aarch64,x86_64}-unknown-linux-gnu
|
||||
```
|
||||
1. You can now exit the container, the package paths displayed at the end under `target` will
|
||||
persist.
|
||||
|
||||
## Adding or amending a deb package
|
||||
The rough overview of steps is:
|
||||
The rough overview of steps is as follows, see further down for details.
|
||||
1. Add cargo-deb specific metadata to the rust package and any static assets. Submit your changes as
|
||||
a PR.
|
||||
2. Add build instructions to the separate packaging repo. Submit your changes as a PR.
|
||||
2. Add build steps to the separate packaging repo. Submit your changes as a PR.
|
||||
3. Go back to the main repo to update the packaging submodule reference to aid running manual dev
|
||||
builds of the new package.
|
||||
|
||||
|
@ -72,8 +60,8 @@ an example, see `unix_integration/resolver/Cargo.toml`
|
|||
### Configuration in the kanidm_ppa_automation repo
|
||||
- The repo is: [kanidm/kanidm_ppa_automation](https://github.com/kanidm/kanidm_ppa_automation)
|
||||
- Changes are needed if a new binary and/or package is added, or if build time dependencies change.
|
||||
- Amend `scripts/crossbuild.sh` build rules to include new binaries or packages with shared
|
||||
libraries. Search for the lines starting with `cross build`.
|
||||
- Amend `scripts/build_native.sh` build rules to include new binaries or packages with shared
|
||||
libraries.
|
||||
- Add any new build time system dependencies to `scripts/install_ci_build_dependencies.sh`, be aware
|
||||
of any difference in package names between Debian & Ubuntu.
|
||||
- Add any new packages to `scripts/build_debs.sh`, search for the line starting with `for package in`.
|
||||
|
|
|
@ -147,8 +147,8 @@ Features or APIs may be removed with 1 release versions notice. Deprecations wil
|
|||
|
||||
### Python module
|
||||
|
||||
The python module will typically trail changes in functionality of the core Rust code, and will be
|
||||
developed as we it for our own needs - please feel free to add functionality or improvements, or
|
||||
The Python module will typically trail changes in functionality of the core Rust code, and has been
|
||||
developed as we have needed it - please feel free to add functionality or improvements, or
|
||||
[ask for them in a Github issue](http://github.com/kanidm/kanidm/issues/new/choose)!
|
||||
|
||||
All code changes will include full type-casting wherever possible.
|
||||
|
|
|
@ -22,6 +22,7 @@ This is a list of supported features and standards within Kanidm.
|
|||
- [RFC4519 LDAP Schema](https://www.rfc-editor.org/rfc/rfc4519)
|
||||
- FreeIPA User Schema
|
||||
- [RFC7644 SCIM Bulk Data Import](https://www.rfc-editor.org/rfc/rfc7644)
|
||||
- NOTE: SCIM is only supported for synchronisation from another IDP at this time.
|
||||
|
||||
# Database
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ You can find the name of your 389 Directory Server instance with:
|
|||
|
||||
```bash
|
||||
# Run on the FreeIPA server
|
||||
dsconf --list
|
||||
dsctl --list
|
||||
> slapd-DEV-KANIDM-COM
|
||||
```
|
||||
|
||||
Using this you can show the current status of the retro changelog plugin to see if you need to
|
||||
|
@ -83,6 +84,20 @@ kanidm-ipa-sync [-c /path/to/kanidm/config] -i /path/to/kanidm-ipa-sync -n
|
|||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync -n
|
||||
```
|
||||
|
||||
As the sync tool is part of the tools container, you can run this with:
|
||||
|
||||
```bash
|
||||
docker run --rm -i -t \
|
||||
--user uid:gid \
|
||||
-p 12345:12345 \
|
||||
-v /etc/kanidm/config:/etc/kanidm/config:ro \
|
||||
-v /path/to/kanidm.ca.pem:/path/to/kanidm.ca.pem:ro
|
||||
-v /path/to/ipa-ca.pem:/etc/kanidm/ipa-ca.pem:ro \
|
||||
-v /path/to/ipa-sync:/etc/kanidm/ipa-sync:ro \
|
||||
kanidm/tools:latest \
|
||||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync -
|
||||
```
|
||||
|
||||
## Running the Sync Tool Automatically
|
||||
|
||||
The sync tool can be run on a schedule if you configure the `schedule` parameter, and provide the
|
||||
|
@ -96,11 +111,14 @@ kanidm-ipa-sync -i /etc/kanidm/ipa-sync --schedule
|
|||
As the sync tool is part of the tools container, you can run this with:
|
||||
|
||||
```bash
|
||||
docker create --name kanidm-ipa-sync \
|
||||
docker run --name kanidm-ipa-sync \
|
||||
--user uid:gid \
|
||||
-p 12345:12345 \
|
||||
-v /etc/kanidm/config:/etc/kanidm/config:ro \
|
||||
-v /path/to/kanidm.ca.pem:/path/to/kanidm.ca.pem:ro
|
||||
-v /path/to/ipa-ca.pem:/etc/kanidm/ipa-ca.pem:ro \
|
||||
-v /path/to/ipa-sync:/etc/kanidm/ipa-sync:ro \
|
||||
kanidm/tools:latest \
|
||||
kanidm-ipa-sync -i /etc/kanidm/ipa-sync --schedule
|
||||
```
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@ sync_token = "eyJhb..."
|
|||
# server in the IPA topology rather than via a load balancer or dns srv records. This
|
||||
# is to prevent replication conflicts and issues due to how 389-ds content sync works.
|
||||
ipa_uri = "ldaps://specific-server.ipa.dev.kanidm.com"
|
||||
# Path to the IPA CA certificate in PEM format.
|
||||
ipa_ca = "/path/to/kanidm-ipa-ca.pem"
|
||||
# Path to the IPA CA certificate in PEM format. This can be found on an IPA server
|
||||
# in the file `/etc/ipa/ca.crt`
|
||||
ipa_ca = "/path/to/ipa-ca.pem"
|
||||
# The DN of an account with content sync rights. By default cn=Directory Manager has
|
||||
# this access.
|
||||
ipa_sync_dn = "cn=Directory Manager"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
## Kanidm minimal Service Configuration - /etc/kanidm/config
|
||||
# For a full example and documentation, see /usr/share/kanidm/kanidm
|
||||
# Kanidm minimal Service Configuration - /etc/kanidm/config
|
||||
# For a full example and documentation, see /usr/share/kanidm/config
|
||||
# or `example/kanidm` in the source repository.
|
||||
|
||||
# Replace this with your kanidmd URI and uncomment the line
|
||||
#uri = "https://idm.example.com"
|
||||
# uri = "https://idm.example.com"
|
||||
verify_ca = true
|
||||
|
|
18
examples/radius.toml
Normal file
18
examples/radius.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
uri = "https://example.com"
|
||||
|
||||
# The auth token for the service account
|
||||
auth_token = "ABC..."
|
||||
|
||||
# default vlan for groups that don't specify one.
|
||||
radius_default_vlan = 99
|
||||
|
||||
# if the user is in one of these Kanidm groups,
|
||||
# then they're allowed to authenticate
|
||||
radius_required_groups = ["radius_access_allowed@idm.example.com"]
|
||||
|
||||
radius_groups = [{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }]
|
||||
|
||||
radius_clients = [
|
||||
{ name = "localhost", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker", ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
28
examples/radius_full.toml
Normal file
28
examples/radius_full.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
uri = "https://example.com" # URL to the Kanidm server
|
||||
verify_hostnames = true # verify the hostname of the Kanidm server
|
||||
verify_ca = true # Strict CA verification
|
||||
|
||||
auth_token = "ABC..." # Auth token for the service account
|
||||
# See: kanidm service-account api-token generate
|
||||
|
||||
# Default vlans for groups that don't specify one.
|
||||
radius_default_vlan = 1
|
||||
|
||||
# A list of Kanidm groups which must be a member
|
||||
# before they can authenticate via RADIUS.
|
||||
radius_required_groups = ["radius_access_allowed@idm.example.com"]
|
||||
|
||||
# A mapping between Kanidm groups and VLANS
|
||||
radius_groups = [{ spn = "radius_access_allowed@idm.example.com", vlan = 10 }]
|
||||
|
||||
# A mapping of clients and their authentication tokens
|
||||
radius_clients = [
|
||||
{ name = "test", ipaddr = "127.0.0.1", secret = "testing123" },
|
||||
{ name = "docker", ipaddr = "172.17.0.0/16", secret = "testing123" },
|
||||
]
|
||||
|
||||
# radius_cert_path = "/etc/raddb/certs/cert.pem"
|
||||
# the signing key for radius TLS
|
||||
# radius_key_path = "/etc/raddb/certs/key.pem"
|
||||
radius_ca_path = "/data/ca.pem" # Path to the kanidm ca
|
||||
# radius_ca_dir = "/data/ca"
|
|
@ -1,3 +1,6 @@
|
|||
# The server configuration file version.
|
||||
version = "2"
|
||||
|
||||
# The webserver bind address. Requires TLS certificates.
|
||||
# If the port is set to 443 you may require the
|
||||
# NET_BIND_SERVICE capability.
|
||||
|
@ -23,7 +26,7 @@ bindaddress = "[::]:443"
|
|||
# The path to the kanidm database.
|
||||
db_path = "/var/lib/private/kanidm/kanidm.db"
|
||||
#
|
||||
# If you have a known filesystem, kanidm can tune the
|
||||
# If you have a known filesystem, kanidm can tune the
|
||||
# database page size to match. Valid choices are:
|
||||
# [zfs, other]
|
||||
# If you are unsure about this leave it as the default
|
||||
|
@ -45,7 +48,7 @@ db_path = "/var/lib/private/kanidm/kanidm.db"
|
|||
# db_arc_size = 2048
|
||||
#
|
||||
# TLS chain and key in pem format. Both must be present.
|
||||
# If the server recieves a SIGHUP, these files will be
|
||||
# If the server receives a SIGHUP, these files will be
|
||||
# re-read and reloaded if their content is valid.
|
||||
tls_chain = "/var/lib/private/kanidm/chain.pem"
|
||||
tls_key = "/var/lib/private/kanidm/key.pem"
|
||||
|
@ -72,7 +75,7 @@ tls_key = "/var/lib/private/kanidm/key.pem"
|
|||
# credentials for accounts including but not limited to
|
||||
# webauthn, oauth tokens, and more.
|
||||
# If you change this value you *must* run
|
||||
# `kanidmd domain_name_change` immediately after.
|
||||
# `kanidmd domain rename` immediately after.
|
||||
domain = "idm.example.com"
|
||||
#
|
||||
# The origin for webauthn. This is the url to the server,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
# The server configuration file version.
|
||||
version = "2"
|
||||
|
||||
# The webserver bind address. Requires TLS certificates.
|
||||
# If the port is set to 443 you may require the
|
||||
# NET_BIND_SERVICE capability.
|
||||
|
@ -23,7 +26,7 @@ bindaddress = "[::]:8443"
|
|||
# The path to the kanidm database.
|
||||
db_path = "/data/kanidm.db"
|
||||
#
|
||||
# If you have a known filesystem, kanidm can tune the
|
||||
# If you have a known filesystem, kanidm can tune the
|
||||
# database page size to match. Valid choices are:
|
||||
# [zfs, other]
|
||||
# If you are unsure about this leave it as the default
|
||||
|
@ -44,7 +47,9 @@ db_path = "/data/kanidm.db"
|
|||
# memory pressure on your system.
|
||||
# db_arc_size = 2048
|
||||
#
|
||||
# TLS chain and key in pem format. Both must be present
|
||||
# TLS chain and key in pem format. Both must be present.
|
||||
# If the server receives a SIGHUP, these files will be
|
||||
# re-read and reloaded if their content is valid.
|
||||
tls_chain = "/data/chain.pem"
|
||||
tls_key = "/data/key.pem"
|
||||
#
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
# The configuration file version.
|
||||
version = '2'
|
||||
|
||||
# ⚠️ Ensure that you have the [kanidm] or other provider sections below
|
||||
# configured else accounts from remote sources will not be available.
|
||||
|
||||
# Kanidm unix will bind all cached credentials to a local Hardware Security
|
||||
# Module (HSM) to prevent exfiltration and attacks against these. In addition,
|
||||
# any internal private keys will also be stored in this HSM.
|
||||
|
@ -136,6 +139,7 @@ version = '2'
|
|||
# allow_local_account_override = ["admin"]
|
||||
|
||||
|
||||
# ========================================
|
||||
# This section enables the Kanidm provider
|
||||
[kanidm]
|
||||
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
## Kanidm Unixd minimal Service Configuration - /etc/kanidm/unixd
|
||||
# Kanidm Unixd minimal Service Configuration - /etc/kanidm/unixd
|
||||
# For a full example and documentation, see /usr/share/kanidm-unixd/unixd
|
||||
# or `example/unixd` in the source repository.
|
||||
# or `example/unixd` in the source repository
|
||||
|
||||
version = '2'
|
||||
|
||||
[kanidm]
|
||||
# default_shell = "/bin/sh"
|
||||
|
||||
# home_attr = "uuid"
|
||||
# home_alias = "spn"
|
||||
# use_etc_skel = false
|
||||
|
||||
|
||||
# Defines a set of POSIX groups where membership of any of these groups
|
||||
# will be allowed to login via PAM.
|
||||
# Replace your group below and uncomment this line:
|
||||
#pam_allowed_login_groups = ["your_posix_login_group"]
|
||||
# will be allowed to login via PAM
|
||||
#
|
||||
# WITHOUT THIS SET, NOBODY WILL BE ABLE TO LOG IN VIA PAM
|
||||
#
|
||||
# Replace your group below and uncomment this line
|
||||
# pam_allowed_login_groups = ["your_posix_login_group"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{ClientError, KanidmClient};
|
||||
use kanidm_proto::constants::ATTR_DOMAIN_ALLOW_EASTER_EGGS;
|
||||
use kanidm_proto::internal::ImageValue;
|
||||
use reqwest::multipart;
|
||||
|
||||
|
@ -8,6 +9,14 @@ impl KanidmClient {
|
|||
self.perform_delete_request("/v1/domain/_image").await
|
||||
}
|
||||
|
||||
pub async fn idm_set_domain_allow_easter_eggs(&self, enable: bool) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
&format!("{}{}", "/v1/domain/_attr/", ATTR_DOMAIN_ALLOW_EASTER_EGGS),
|
||||
vec![enable.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Add or update the domain logo/image
|
||||
pub async fn idm_domain_update_image(&self, image: ImageValue) -> Result<(), ClientError> {
|
||||
let file_content_type = image.filetype.as_content_type_str();
|
||||
|
|
|
@ -37,6 +37,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_authsession_expiry_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/authsession_expiry", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_credential_type_minimum_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -61,6 +69,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_password_minimum_length_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/auth_password_minimum_length",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_privilege_expiry_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -73,6 +92,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_privilege_expiry_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/privilege_expiry", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_webauthn_attestation_set(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -85,6 +112,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_webauthn_attestation_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/webauthn_attestation_ca_list",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_results(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -97,6 +135,14 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_results_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!("/v1/group/{}/_attr/limit_search_max_results", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_filter_test(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -109,6 +155,17 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_limit_search_max_filter_test_reset(
|
||||
&self,
|
||||
id: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_delete_request(&format!(
|
||||
"/v1/group/{}/_attr/limit_search_max_filter_test",
|
||||
id
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn group_account_policy_allow_primary_cred_fallback(
|
||||
&self,
|
||||
id: &str,
|
||||
|
@ -138,4 +195,20 @@ impl KanidmClient {
|
|||
self.perform_get_request(&format!("/v1/group/{}/_attr/mail", id))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_group_purge_description(&self, id: &str) -> Result<(), ClientError> {
|
||||
self.idm_group_purge_attr(id, "description").await
|
||||
}
|
||||
|
||||
pub async fn idm_group_set_description(
|
||||
&self,
|
||||
id: &str,
|
||||
description: &str,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
&format!("/v1/group/{}/_attr/description", id),
|
||||
&[description],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,11 +27,12 @@ use std::time::Duration;
|
|||
|
||||
use compact_jwt::Jwk;
|
||||
|
||||
pub use http;
|
||||
use kanidm_proto::constants::uri::V1_AUTH_VALID;
|
||||
use kanidm_proto::constants::{
|
||||
ATTR_DOMAIN_DISPLAY_NAME, ATTR_DOMAIN_LDAP_BASEDN, ATTR_DOMAIN_SSID, ATTR_ENTRY_MANAGED_BY,
|
||||
ATTR_KEY_ACTION_REVOKE, ATTR_LDAP_ALLOW_UNIX_PW_BIND, ATTR_NAME, CLIENT_TOKEN_CACHE, KOPID,
|
||||
KSESSIONID, KVERSION,
|
||||
ATTR_KEY_ACTION_REVOKE, ATTR_LDAP_ALLOW_UNIX_PW_BIND, ATTR_LDAP_MAX_QUERYABLE_ATTRS, ATTR_NAME,
|
||||
CLIENT_TOKEN_CACHE, KOPID, KSESSIONID, KVERSION,
|
||||
};
|
||||
use kanidm_proto::internal::*;
|
||||
use kanidm_proto::v1::*;
|
||||
|
@ -94,7 +95,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.
|
||||
///
|
||||
|
@ -137,6 +138,7 @@ pub struct KanidmClientBuilder {
|
|||
use_system_proxies: bool,
|
||||
/// Where to store auth tokens, only use in testing!
|
||||
token_cache_path: Option<String>,
|
||||
disable_system_ca_store: bool,
|
||||
}
|
||||
|
||||
impl Display for KanidmClientBuilder {
|
||||
|
@ -170,33 +172,6 @@ impl Display for KanidmClientBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kanidmclientbuilder_display() {
|
||||
let defaultclient = KanidmClientBuilder::default();
|
||||
println!("{}", defaultclient);
|
||||
assert!(defaultclient.to_string().contains("verify_ca"));
|
||||
|
||||
let testclient = KanidmClientBuilder {
|
||||
address: Some("https://example.com".to_string()),
|
||||
verify_ca: true,
|
||||
verify_hostnames: true,
|
||||
ca: None,
|
||||
connect_timeout: Some(420),
|
||||
request_timeout: Some(69),
|
||||
use_system_proxies: true,
|
||||
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
|
||||
};
|
||||
println!("testclient {}", testclient);
|
||||
assert!(testclient.to_string().contains("verify_ca: true"));
|
||||
assert!(testclient.to_string().contains("verify_hostnames: true"));
|
||||
|
||||
let badness = testclient.danger_accept_invalid_hostnames(true);
|
||||
let badness = badness.danger_accept_invalid_certs(true);
|
||||
println!("badness: {}", badness);
|
||||
assert!(badness.to_string().contains("verify_ca: false"));
|
||||
assert!(badness.to_string().contains("verify_hostnames: false"));
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KanidmClient {
|
||||
pub(crate) client: reqwest::Client,
|
||||
|
@ -233,6 +208,7 @@ impl KanidmClientBuilder {
|
|||
request_timeout: None,
|
||||
use_system_proxies: true,
|
||||
token_cache_path: None,
|
||||
disable_system_ca_store: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,6 +266,7 @@ impl KanidmClientBuilder {
|
|||
request_timeout,
|
||||
use_system_proxies,
|
||||
token_cache_path,
|
||||
disable_system_ca_store,
|
||||
} = self;
|
||||
// Process and apply all our options if they exist.
|
||||
let address = match kcc.uri {
|
||||
|
@ -316,6 +293,7 @@ impl KanidmClientBuilder {
|
|||
request_timeout,
|
||||
use_system_proxies,
|
||||
token_cache_path,
|
||||
disable_system_ca_store,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -416,6 +394,16 @@ impl KanidmClientBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enable or disable the native ca roots. By default these roots are enabled.
|
||||
pub fn enable_native_ca_roots(self, enable: bool) -> Self {
|
||||
KanidmClientBuilder {
|
||||
// We have to flip the bool state here due to Default on bool being false
|
||||
// and we want our options to be positive to a native speaker.
|
||||
disable_system_ca_store: !enable,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn danger_accept_invalid_hostnames(self, accept_invalid_hostnames: bool) -> Self {
|
||||
KanidmClientBuilder {
|
||||
// We have to flip the bool state here due to english language.
|
||||
|
@ -453,6 +441,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.
|
||||
|
@ -520,6 +515,7 @@ impl KanidmClientBuilder {
|
|||
// implement sticky sessions with cookies.
|
||||
.cookie_store(true)
|
||||
.cookie_provider(client_cookies.clone())
|
||||
.tls_built_in_native_certs(!self.disable_system_ca_store)
|
||||
.danger_accept_invalid_hostnames(!self.verify_hostnames)
|
||||
.danger_accept_invalid_certs(!self.verify_ca);
|
||||
|
||||
|
@ -572,32 +568,6 @@ impl KanidmClientBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_url() {
|
||||
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
|
||||
let client: KanidmClient = KanidmClientBuilder::new()
|
||||
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.get_url(),
|
||||
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
client.make_url("/hello"),
|
||||
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
|
||||
let client: KanidmClient = KanidmClientBuilder::new()
|
||||
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.make_url("hello"),
|
||||
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
/// This is probably pretty jank but it works and was pulled from here:
|
||||
/// <https://github.com/seanmonstar/reqwest/issues/1602#issuecomment-1220996681>
|
||||
fn find_reqwest_error_source<E: std::error::Error + 'static>(
|
||||
|
@ -616,6 +586,11 @@ fn find_reqwest_error_source<E: std::error::Error + 'static>(
|
|||
}
|
||||
|
||||
impl KanidmClient {
|
||||
/// Access the underlying reqwest client that has been configured for this Kanidm server
|
||||
pub fn client(&self) -> &reqwest::Client {
|
||||
&self.client
|
||||
}
|
||||
|
||||
pub fn get_origin(&self) -> &Url {
|
||||
&self.origin
|
||||
}
|
||||
|
@ -2075,6 +2050,18 @@ impl KanidmClient {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Sets the maximum number of LDAP attributes that can be queryed in a single operation
|
||||
pub async fn idm_domain_set_ldap_max_queryable_attrs(
|
||||
&self,
|
||||
max_queryable_attrs: usize,
|
||||
) -> Result<(), ClientError> {
|
||||
self.perform_put_request(
|
||||
&format!("/v1/domain/_attr/{}", ATTR_LDAP_MAX_QUERYABLE_ATTRS),
|
||||
vec![max_queryable_attrs.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn idm_set_ldap_allow_unix_password_bind(
|
||||
&self,
|
||||
enable: bool,
|
||||
|
@ -2155,31 +2142,97 @@ impl KanidmClient {
|
|||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_no_client_version_check_on_502() {
|
||||
let res = reqwest::Response::from(
|
||||
http::Response::builder()
|
||||
.status(StatusCode::GATEWAY_TIMEOUT)
|
||||
.body("")
|
||||
.unwrap(),
|
||||
);
|
||||
let client = KanidmClientBuilder::new()
|
||||
.address("http://localhost:8080".to_string())
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
|
||||
client.expect_version(&res).await;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{KanidmClient, KanidmClientBuilder};
|
||||
use kanidm_proto::constants::CLIENT_TOKEN_CACHE;
|
||||
use reqwest::StatusCode;
|
||||
use url::Url;
|
||||
|
||||
let res = reqwest::Response::from(
|
||||
http::Response::builder()
|
||||
.status(StatusCode::BAD_GATEWAY)
|
||||
.body("")
|
||||
.unwrap(),
|
||||
);
|
||||
let client = KanidmClientBuilder::new()
|
||||
.address("http://localhost:8080".to_string())
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
|
||||
client.expect_version(&res).await;
|
||||
#[tokio::test]
|
||||
async fn test_no_client_version_check_on_502() {
|
||||
let res = reqwest::Response::from(
|
||||
http::Response::builder()
|
||||
.status(StatusCode::GATEWAY_TIMEOUT)
|
||||
.body("")
|
||||
.unwrap(),
|
||||
);
|
||||
let client = KanidmClientBuilder::new()
|
||||
.address("http://localhost:8080".to_string())
|
||||
.enable_native_ca_roots(false)
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
eprintln!("This should pass because we are returning 504 and shouldn't check version...");
|
||||
client.expect_version(&res).await;
|
||||
|
||||
let res = reqwest::Response::from(
|
||||
http::Response::builder()
|
||||
.status(StatusCode::BAD_GATEWAY)
|
||||
.body("")
|
||||
.unwrap(),
|
||||
);
|
||||
let client = KanidmClientBuilder::new()
|
||||
.address("http://localhost:8080".to_string())
|
||||
.enable_native_ca_roots(false)
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
eprintln!("This should pass because we are returning 502 and shouldn't check version...");
|
||||
client.expect_version(&res).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_url() {
|
||||
use kanidm_proto::constants::DEFAULT_SERVER_ADDRESS;
|
||||
let client: KanidmClient = KanidmClientBuilder::new()
|
||||
.address(format!("https://{}", DEFAULT_SERVER_ADDRESS))
|
||||
.enable_native_ca_roots(false)
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.get_url(),
|
||||
Url::parse(&format!("https://{}", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
client.make_url("/hello"),
|
||||
Url::parse(&format!("https://{}/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
|
||||
let client: KanidmClient = KanidmClientBuilder::new()
|
||||
.address(format!("https://{}/cheese/", DEFAULT_SERVER_ADDRESS))
|
||||
.enable_native_ca_roots(false)
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
client.make_url("hello"),
|
||||
Url::parse(&format!("https://{}/cheese/hello", DEFAULT_SERVER_ADDRESS)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kanidmclientbuilder_display() {
|
||||
let defaultclient = KanidmClientBuilder::default();
|
||||
println!("{}", defaultclient);
|
||||
assert!(defaultclient.to_string().contains("verify_ca"));
|
||||
|
||||
let testclient = KanidmClientBuilder {
|
||||
address: Some("https://example.com".to_string()),
|
||||
verify_ca: true,
|
||||
verify_hostnames: true,
|
||||
ca: None,
|
||||
connect_timeout: Some(420),
|
||||
request_timeout: Some(69),
|
||||
use_system_proxies: true,
|
||||
token_cache_path: Some(CLIENT_TOKEN_CACHE.to_string()),
|
||||
disable_system_ca_store: false,
|
||||
};
|
||||
println!("testclient {}", testclient);
|
||||
assert!(testclient.to_string().contains("verify_ca: true"));
|
||||
assert!(testclient.to_string().contains("verify_hostnames: true"));
|
||||
|
||||
let badness = testclient.danger_accept_invalid_hostnames(true);
|
||||
let badness = badness.danger_accept_invalid_certs(true);
|
||||
println!("badness: {}", badness);
|
||||
assert!(badness.to_string().contains("verify_ca: false"));
|
||||
assert!(badness.to_string().contains("verify_hostnames: false"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -434,6 +434,8 @@ impl KanidmClient {
|
|||
id: &str,
|
||||
origin: &Url,
|
||||
) -> Result<(), ClientError> {
|
||||
// TODO: should we normalise loopback origins, so when a user specifies `http://localhost/foo` we store it as `http://[::1]/foo`?
|
||||
|
||||
let url_to_add = &[origin.as_str()];
|
||||
self.perform_post_request(
|
||||
format!("/v1/oauth2/{}/_attr/{}", id, ATTR_OAUTH2_RS_ORIGIN).as_str(),
|
||||
|
|
|
@ -33,5 +33,12 @@ tracing = { workspace = true }
|
|||
uuid = { workspace = true }
|
||||
x509-cert = { workspace = true, features = ["pem"] }
|
||||
|
||||
md-5 = { workspace = true }
|
||||
sha-crypt = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
sketching = { workspace = true }
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["openssl-sys"]
|
||||
|
|
99
libs/crypto/src/crypt_md5.rs
Normal file
99
libs/crypto/src/crypt_md5.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use md5::{Digest, Md5};
|
||||
use std::cmp::min;
|
||||
|
||||
/// Maximium salt length.
|
||||
const MD5_MAGIC: &str = "$1$";
|
||||
const MD5_TRANSPOSE: &[u8] = b"\x0c\x06\x00\x0d\x07\x01\x0e\x08\x02\x0f\x09\x03\x05\x0a\x04\x0b";
|
||||
|
||||
const CRYPT_HASH64: &[u8] = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
pub fn md5_sha2_hash64_encode(bs: &[u8]) -> String {
|
||||
let ngroups = bs.len().div_ceil(3);
|
||||
let mut out = String::with_capacity(ngroups * 4);
|
||||
for g in 0..ngroups {
|
||||
let mut g_idx = g * 3;
|
||||
let mut enc = 0u32;
|
||||
for _ in 0..3 {
|
||||
let b = (if g_idx < bs.len() { bs[g_idx] } else { 0 }) as u32;
|
||||
enc >>= 8;
|
||||
enc |= b << 16;
|
||||
g_idx += 1;
|
||||
}
|
||||
for _ in 0..4 {
|
||||
out.push(char::from_u32(CRYPT_HASH64[(enc & 0x3F) as usize] as u32).unwrap_or('!'));
|
||||
enc >>= 6;
|
||||
}
|
||||
}
|
||||
match bs.len() % 3 {
|
||||
1 => {
|
||||
out.pop();
|
||||
out.pop();
|
||||
}
|
||||
2 => {
|
||||
out.pop();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn do_md5_crypt(pass: &[u8], salt: &[u8]) -> Vec<u8> {
|
||||
let mut dgst_b = Md5::new();
|
||||
dgst_b.update(pass);
|
||||
dgst_b.update(salt);
|
||||
dgst_b.update(pass);
|
||||
let mut hash_b = dgst_b.finalize();
|
||||
|
||||
let mut dgst_a = Md5::new();
|
||||
dgst_a.update(pass);
|
||||
dgst_a.update(MD5_MAGIC.as_bytes());
|
||||
dgst_a.update(salt);
|
||||
|
||||
let mut plen = pass.len();
|
||||
while plen > 0 {
|
||||
dgst_a.update(&hash_b[..min(plen, 16)]);
|
||||
if plen < 16 {
|
||||
break;
|
||||
}
|
||||
plen -= 16;
|
||||
}
|
||||
|
||||
plen = pass.len();
|
||||
while plen > 0 {
|
||||
if plen & 1 == 0 {
|
||||
dgst_a.update(&pass[..1])
|
||||
} else {
|
||||
dgst_a.update([0u8])
|
||||
}
|
||||
plen >>= 1;
|
||||
}
|
||||
|
||||
let mut hash_a = dgst_a.finalize();
|
||||
|
||||
for r in 0..1000 {
|
||||
let mut dgst_a = Md5::new();
|
||||
if r % 2 == 1 {
|
||||
dgst_a.update(pass);
|
||||
} else {
|
||||
dgst_a.update(hash_a);
|
||||
}
|
||||
if r % 3 > 0 {
|
||||
dgst_a.update(salt);
|
||||
}
|
||||
if r % 7 > 0 {
|
||||
dgst_a.update(pass);
|
||||
}
|
||||
if r % 2 == 0 {
|
||||
dgst_a.update(pass);
|
||||
} else {
|
||||
dgst_a.update(hash_a);
|
||||
}
|
||||
hash_a = dgst_a.finalize();
|
||||
}
|
||||
|
||||
for (i, &ti) in MD5_TRANSPOSE.iter().enumerate() {
|
||||
hash_b[i] = hash_a[ti as usize];
|
||||
}
|
||||
|
||||
md5_sha2_hash64_encode(&hash_b).into_bytes()
|
||||
}
|
|
@ -11,26 +11,24 @@
|
|||
#![deny(clippy::unreachable)]
|
||||
|
||||
use argon2::{Algorithm, Argon2, Params, PasswordHash, Version};
|
||||
use base64::engine::general_purpose;
|
||||
use base64::engine::GeneralPurpose;
|
||||
use base64::{alphabet, Engine};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use base64::engine::general_purpose;
|
||||
use base64urlsafedata::Base64UrlSafeData;
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use kanidm_hsm_crypto::{HmacKey, Tpm};
|
||||
use kanidm_proto::internal::OperationError;
|
||||
use openssl::error::ErrorStack as OpenSSLErrorStack;
|
||||
use openssl::hash::{self, MessageDigest};
|
||||
use openssl::nid::Nid;
|
||||
use openssl::pkcs5::pbkdf2_hmac;
|
||||
use openssl::sha::{Sha1, Sha256, Sha512};
|
||||
use rand::Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::time::{Duration, Instant};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use kanidm_hsm_crypto::{HmacKey, Tpm};
|
||||
|
||||
mod crypt_md5;
|
||||
pub mod mtls;
|
||||
pub mod prelude;
|
||||
pub mod serialise;
|
||||
|
@ -84,6 +82,7 @@ pub enum CryptoError {
|
|||
Argon2,
|
||||
Argon2Version,
|
||||
Argon2Parameters,
|
||||
Crypt,
|
||||
}
|
||||
|
||||
impl From<OpenSSLErrorStack> for CryptoError {
|
||||
|
@ -137,65 +136,15 @@ pub enum DbPasswordV1 {
|
|||
SHA512(Vec<u8>),
|
||||
SSHA512(Vec<u8>, Vec<u8>),
|
||||
NT_MD4(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum ReplPasswordV1 {
|
||||
TPM_ARGON2ID {
|
||||
m_cost: u32,
|
||||
t_cost: u32,
|
||||
p_cost: u32,
|
||||
version: u32,
|
||||
salt: Base64UrlSafeData,
|
||||
key: Base64UrlSafeData,
|
||||
CRYPT_MD5 {
|
||||
s: Base64UrlSafeData,
|
||||
h: Base64UrlSafeData,
|
||||
},
|
||||
ARGON2ID {
|
||||
m_cost: u32,
|
||||
t_cost: u32,
|
||||
p_cost: u32,
|
||||
version: u32,
|
||||
salt: Base64UrlSafeData,
|
||||
key: Base64UrlSafeData,
|
||||
CRYPT_SHA256 {
|
||||
h: String,
|
||||
},
|
||||
PBKDF2 {
|
||||
cost: usize,
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
PBKDF2_SHA1 {
|
||||
cost: usize,
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
PBKDF2_SHA512 {
|
||||
cost: usize,
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SHA1 {
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SSHA1 {
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SHA256 {
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SSHA256 {
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SHA512 {
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
SSHA512 {
|
||||
salt: Base64UrlSafeData,
|
||||
hash: Base64UrlSafeData,
|
||||
},
|
||||
NT_MD4 {
|
||||
hash: Base64UrlSafeData,
|
||||
CRYPT_SHA512 {
|
||||
h: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -214,6 +163,9 @@ impl fmt::Debug for DbPasswordV1 {
|
|||
DbPasswordV1::SHA512(_) => write!(f, "SHA512"),
|
||||
DbPasswordV1::SSHA512(_, _) => write!(f, "SSHA512"),
|
||||
DbPasswordV1::NT_MD4(_) => write!(f, "NT_MD4"),
|
||||
DbPasswordV1::CRYPT_MD5 { .. } => write!(f, "CRYPT_MD5"),
|
||||
DbPasswordV1::CRYPT_SHA256 { .. } => write!(f, "CRYPT_SHA256"),
|
||||
DbPasswordV1::CRYPT_SHA512 { .. } => write!(f, "CRYPT_SHA512"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -436,6 +388,16 @@ enum Kdf {
|
|||
SSHA512(Vec<u8>, Vec<u8>),
|
||||
// hash
|
||||
NT_MD4(Vec<u8>),
|
||||
CRYPT_MD5 {
|
||||
s: Vec<u8>,
|
||||
h: Vec<u8>,
|
||||
},
|
||||
CRYPT_SHA256 {
|
||||
h: String,
|
||||
},
|
||||
CRYPT_SHA512 {
|
||||
h: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -498,78 +460,17 @@ impl TryFrom<DbPasswordV1> for Password {
|
|||
DbPasswordV1::NT_MD4(h) => Ok(Password {
|
||||
material: Kdf::NT_MD4(h),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ReplPasswordV1> for Password {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: &ReplPasswordV1) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
ReplPasswordV1::TPM_ARGON2ID {
|
||||
m_cost,
|
||||
t_cost,
|
||||
p_cost,
|
||||
version,
|
||||
salt,
|
||||
key,
|
||||
} => Ok(Password {
|
||||
material: Kdf::TPM_ARGON2ID {
|
||||
m_cost: *m_cost,
|
||||
t_cost: *t_cost,
|
||||
p_cost: *p_cost,
|
||||
version: *version,
|
||||
salt: salt.to_vec(),
|
||||
key: key.to_vec(),
|
||||
DbPasswordV1::CRYPT_MD5 { s, h } => Ok(Password {
|
||||
material: Kdf::CRYPT_MD5 {
|
||||
s: s.into(),
|
||||
h: h.into(),
|
||||
},
|
||||
}),
|
||||
ReplPasswordV1::ARGON2ID {
|
||||
m_cost,
|
||||
t_cost,
|
||||
p_cost,
|
||||
version,
|
||||
salt,
|
||||
key,
|
||||
} => Ok(Password {
|
||||
material: Kdf::ARGON2ID {
|
||||
m_cost: *m_cost,
|
||||
t_cost: *t_cost,
|
||||
p_cost: *p_cost,
|
||||
version: *version,
|
||||
salt: salt.to_vec(),
|
||||
key: key.to_vec(),
|
||||
},
|
||||
DbPasswordV1::CRYPT_SHA256 { h } => Ok(Password {
|
||||
material: Kdf::CRYPT_SHA256 { h },
|
||||
}),
|
||||
ReplPasswordV1::PBKDF2 { cost, salt, hash } => Ok(Password {
|
||||
material: Kdf::PBKDF2(*cost, salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::PBKDF2_SHA1 { cost, salt, hash } => Ok(Password {
|
||||
material: Kdf::PBKDF2_SHA1(*cost, salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::PBKDF2_SHA512 { cost, salt, hash } => Ok(Password {
|
||||
material: Kdf::PBKDF2_SHA512(*cost, salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SHA1 { hash } => Ok(Password {
|
||||
material: Kdf::SHA1(hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SSHA1 { salt, hash } => Ok(Password {
|
||||
material: Kdf::SSHA1(salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SHA256 { hash } => Ok(Password {
|
||||
material: Kdf::SHA256(hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SSHA256 { salt, hash } => Ok(Password {
|
||||
material: Kdf::SSHA256(salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SHA512 { hash } => Ok(Password {
|
||||
material: Kdf::SHA512(hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::SSHA512 { salt, hash } => Ok(Password {
|
||||
material: Kdf::SSHA512(salt.to_vec(), hash.to_vec()),
|
||||
}),
|
||||
ReplPasswordV1::NT_MD4 { hash } => Ok(Password {
|
||||
material: Kdf::NT_MD4(hash.to_vec()),
|
||||
DbPasswordV1::CRYPT_SHA512 { h } => Ok(Password {
|
||||
material: Kdf::CRYPT_SHA256 { h },
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -662,9 +563,47 @@ 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(crypt) = value
|
||||
.strip_prefix("{crypt}")
|
||||
.or_else(|| value.strip_prefix("{CRYPT}"))
|
||||
{
|
||||
if let Some(crypt_md5_phc) = crypt.strip_prefix("$1$") {
|
||||
let (salt, hash) = crypt_md5_phc.split_once('$').ok_or(())?;
|
||||
|
||||
// These are a hash64 format, so leave them as bytes, don't try
|
||||
// to decode.
|
||||
let s = salt.as_bytes().to_vec();
|
||||
let h = hash.as_bytes().to_vec();
|
||||
|
||||
return Ok(Password {
|
||||
material: Kdf::CRYPT_MD5 { s, h },
|
||||
});
|
||||
}
|
||||
|
||||
if crypt.starts_with("$5$") {
|
||||
return Ok(Password {
|
||||
material: Kdf::CRYPT_SHA256 {
|
||||
h: crypt.to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if crypt.starts_with("$6$") {
|
||||
return Ok(Password {
|
||||
material: Kdf::CRYPT_SHA512 {
|
||||
h: crypt.to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
} // End crypt
|
||||
|
||||
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 +613,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 +627,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 +642,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 +658,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 +673,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(|_| ())?;
|
||||
|
@ -1223,6 +1177,20 @@ impl Password {
|
|||
})
|
||||
.map(|chal_key| chal_key.as_ref() == key)
|
||||
}
|
||||
(Kdf::CRYPT_MD5 { s, h }, _) => {
|
||||
let chal_key = crypt_md5::do_md5_crypt(cleartext.as_bytes(), s);
|
||||
Ok(chal_key == *h)
|
||||
}
|
||||
(Kdf::CRYPT_SHA256 { h }, _) => {
|
||||
let is_valid = sha_crypt::sha256_check(cleartext, h.as_str()).is_ok();
|
||||
|
||||
Ok(is_valid)
|
||||
}
|
||||
(Kdf::CRYPT_SHA512 { h }, _) => {
|
||||
let is_valid = sha_crypt::sha512_check(cleartext, h.as_str()).is_ok();
|
||||
|
||||
Ok(is_valid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1274,80 +1242,12 @@ impl Password {
|
|||
Kdf::SHA512(hash) => DbPasswordV1::SHA512(hash.clone()),
|
||||
Kdf::SSHA512(salt, hash) => DbPasswordV1::SSHA512(salt.clone(), hash.clone()),
|
||||
Kdf::NT_MD4(hash) => DbPasswordV1::NT_MD4(hash.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_repl_v1(&self) -> ReplPasswordV1 {
|
||||
match &self.material {
|
||||
Kdf::TPM_ARGON2ID {
|
||||
m_cost,
|
||||
t_cost,
|
||||
p_cost,
|
||||
version,
|
||||
salt,
|
||||
key,
|
||||
} => ReplPasswordV1::TPM_ARGON2ID {
|
||||
m_cost: *m_cost,
|
||||
t_cost: *t_cost,
|
||||
p_cost: *p_cost,
|
||||
version: *version,
|
||||
salt: salt.clone().into(),
|
||||
key: key.clone().into(),
|
||||
},
|
||||
Kdf::ARGON2ID {
|
||||
m_cost,
|
||||
t_cost,
|
||||
p_cost,
|
||||
version,
|
||||
salt,
|
||||
key,
|
||||
} => ReplPasswordV1::ARGON2ID {
|
||||
m_cost: *m_cost,
|
||||
t_cost: *t_cost,
|
||||
p_cost: *p_cost,
|
||||
version: *version,
|
||||
salt: salt.clone().into(),
|
||||
key: key.clone().into(),
|
||||
},
|
||||
Kdf::PBKDF2(cost, salt, hash) => ReplPasswordV1::PBKDF2 {
|
||||
cost: *cost,
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::PBKDF2_SHA1(cost, salt, hash) => ReplPasswordV1::PBKDF2_SHA1 {
|
||||
cost: *cost,
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::PBKDF2_SHA512(cost, salt, hash) => ReplPasswordV1::PBKDF2_SHA512 {
|
||||
cost: *cost,
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SHA1(hash) => ReplPasswordV1::SHA1 {
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SSHA1(salt, hash) => ReplPasswordV1::SSHA1 {
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SHA256(hash) => ReplPasswordV1::SHA256 {
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SSHA256(salt, hash) => ReplPasswordV1::SSHA256 {
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SHA512(hash) => ReplPasswordV1::SHA512 {
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::SSHA512(salt, hash) => ReplPasswordV1::SSHA512 {
|
||||
salt: salt.clone().into(),
|
||||
hash: hash.clone().into(),
|
||||
},
|
||||
Kdf::NT_MD4(hash) => ReplPasswordV1::NT_MD4 {
|
||||
hash: hash.clone().into(),
|
||||
Kdf::CRYPT_MD5 { s, h } => DbPasswordV1::CRYPT_MD5 {
|
||||
s: s.clone().into(),
|
||||
h: h.clone().into(),
|
||||
},
|
||||
Kdf::CRYPT_SHA256 { h } => DbPasswordV1::CRYPT_SHA256 { h: h.clone() },
|
||||
Kdf::CRYPT_SHA512 { h } => DbPasswordV1::CRYPT_SHA512 { h: h.clone() },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1383,7 +1283,10 @@ impl Password {
|
|||
| Kdf::SSHA256(_, _)
|
||||
| Kdf::SHA512(_)
|
||||
| Kdf::SSHA512(_, _)
|
||||
| Kdf::NT_MD4(_) => true,
|
||||
| Kdf::NT_MD4(_)
|
||||
| Kdf::CRYPT_MD5 { .. }
|
||||
| Kdf::CRYPT_SHA256 { .. }
|
||||
| Kdf::CRYPT_SHA512 { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1441,8 +1344,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 +1358,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 +1372,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 +1386,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 +1400,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 +1414,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));
|
||||
|
@ -1617,6 +1544,39 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_from_crypt_md5() {
|
||||
sketching::test_init();
|
||||
let im_pw = "{crypt}$1$zaRIAsoe$7887GzjDTrst0XbDPpF5m.";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_from_crypt_sha256() {
|
||||
sketching::test_init();
|
||||
let im_pw = "{crypt}$5$3UzV7Sut8EHCUxlN$41V.jtMQmFAOucqI4ImFV43r.bRLjPlN.hyfoCdmGE2";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_from_crypt_sha512() {
|
||||
sketching::test_init();
|
||||
let im_pw = "{crypt}$6$aXn8azL8DXUyuMvj$9aJJC/KEUwygIpf2MTqjQa.f0MEXNg2cGFc62Fet8XpuDVDedM05CweAlxW6GWxnmHqp14CRf6zU7OQoE/bCu0";
|
||||
let password = "password";
|
||||
let r = Password::try_from(im_pw).expect("Failed to parse");
|
||||
|
||||
assert!(r.requires_upgrade());
|
||||
assert!(r.verify(password).unwrap_or(false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_password_argon2id_hsm_bind() {
|
||||
sketching::test_init();
|
||||
|
|
|
@ -16,8 +16,5 @@ doctest = false
|
|||
|
||||
[dependencies]
|
||||
|
||||
[target.'cfg(target_family = "windows")'.dependencies]
|
||||
whoami = { workspace = true }
|
||||
|
||||
[target.'cfg(not(target_family = "windows"))'.dependencies]
|
||||
kanidm_utils_users = { workspace = true }
|
||||
|
|
|
@ -3,6 +3,9 @@ use std::fs::Metadata;
|
|||
#[cfg(target_os = "freebsd")]
|
||||
use std::os::freebsd::fs::MetadataExt;
|
||||
|
||||
#[cfg(target_os = "openbsd")]
|
||||
use std::os::openbsd::fs::MetadataExt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
|
||||
|
@ -12,6 +15,9 @@ use std::os::macos::fs::MetadataExt;
|
|||
#[cfg(target_os = "illumos")]
|
||||
use std::os::illumos::fs::MetadataExt;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
use std::os::android::fs::MetadataExt;
|
||||
|
||||
use kanidm_utils_users::{get_current_gid, get_current_uid};
|
||||
|
||||
use std::fmt;
|
||||
|
|
|
@ -28,3 +28,7 @@ toml = { workspace = true }
|
|||
[build-dependencies]
|
||||
base64 = { workspace = true }
|
||||
gix = { workspace = true, default-features = false }
|
||||
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["gix"]
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "/hpkg"
|
||||
# Don't set the cpu_flags to autodetect for this platform
|
||||
# cpu_flags = "none"
|
||||
admin_bind_path = "/data/kanidmd.sock"
|
||||
default_config_path = "/data/server.toml"
|
||||
default_unix_shell_path = "/bin/false"
|
||||
server_admin_bind_path = "/data/kanidmd.sock"
|
||||
server_ui_pkg_path = "/hpkg"
|
||||
server_config_path = "/data/server.toml"
|
||||
client_config_path = "/data/config"
|
||||
resolver_config_path = "/data/unixd"
|
||||
resolver_unix_shell_path = "/bin/false"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "../core/static"
|
||||
# Set to native for developer machines.
|
||||
cpu_flags = "native"
|
||||
admin_bind_path = "/tmp/kanidmd.sock"
|
||||
default_config_path = "../../examples/insecure_server.toml"
|
||||
default_unix_shell_path = "/bin/bash"
|
||||
server_admin_bind_path = "/tmp/kanidmd.sock"
|
||||
server_ui_pkg_path = "../core/static"
|
||||
server_config_path = "../../examples/insecure_server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
resolver_config_path = "/tmp/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
||||
|
|
14
libs/profiles/release_debian.toml
Normal file
14
libs/profiles/release_debian.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
# The main difference from the release_linux profile is using
|
||||
# per-package shared directories for a clearer separation and
|
||||
# thus more consistent install & sysadmin experience.
|
||||
|
||||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/share/kanidmd/static"
|
||||
server_config_path = "/etc/kanidmd/server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
# TODO: unixd should migrate to it's own config dir as part of the sparkled migration.
|
||||
# No point in doing two back to back migrations.
|
||||
resolver_config_path = "/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
8
libs/profiles/release_freebsd.toml
Normal file
8
libs/profiles/release_freebsd.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/local/share/kanidm/ui/hpkg"
|
||||
server_config_path = "/usr/local/etc/kanidm/server.toml"
|
||||
client_config_path = "/usr/local/etc/kanidm/config"
|
||||
resolver_config_path = "/usr/local/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/sh"
|
|
@ -1,6 +1,8 @@
|
|||
htmx_ui_pkg_path = "/usr/share/kanidm/ui/hpkg"
|
||||
# Don't set the value for autodetect
|
||||
# cpu_flags = "none"
|
||||
admin_bind_path = "/var/run/kanidmd/sock"
|
||||
default_config_path = "/etc/kanidm/server.toml"
|
||||
default_unix_shell_path = "/bin/bash"
|
||||
server_admin_bind_path = "/var/run/kanidmd/sock"
|
||||
server_ui_pkg_path = "/usr/share/kanidm/ui/hpkg"
|
||||
server_config_path = "/etc/kanidm/server.toml"
|
||||
client_config_path = "/etc/kanidm/config"
|
||||
resolver_config_path = "/etc/kanidm/unixd"
|
||||
resolver_unix_shell_path = "/bin/bash"
|
||||
|
|
|
@ -54,12 +54,14 @@ impl std::fmt::Display for CpuOptLevel {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct ProfileConfig {
|
||||
htmx_ui_pkg_path: String,
|
||||
#[serde(default)]
|
||||
cpu_flags: CpuOptLevel,
|
||||
admin_bind_path: String,
|
||||
default_config_path: String,
|
||||
default_unix_shell_path: String,
|
||||
server_admin_bind_path: String,
|
||||
server_config_path: String,
|
||||
server_ui_pkg_path: String,
|
||||
client_config_path: String,
|
||||
resolver_config_path: String,
|
||||
resolver_unix_shell_path: String,
|
||||
}
|
||||
|
||||
pub fn apply_profile() {
|
||||
|
@ -127,19 +129,27 @@ pub fn apply_profile() {
|
|||
println!("cargo:rustc-env=KANIDM_PROFILE_NAME={}", profile);
|
||||
println!("cargo:rustc-env=KANIDM_CPU_FLAGS={}", profile_cfg.cpu_flags);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_HTMX_UI_PKG_PATH={}",
|
||||
profile_cfg.htmx_ui_pkg_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_UI_PKG_PATH={}",
|
||||
profile_cfg.server_ui_pkg_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_ADMIN_BIND_PATH={}",
|
||||
profile_cfg.admin_bind_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_ADMIN_BIND_PATH={}",
|
||||
profile_cfg.server_admin_bind_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_DEFAULT_CONFIG_PATH={}",
|
||||
profile_cfg.default_config_path
|
||||
"cargo:rustc-env=KANIDM_SERVER_CONFIG_PATH={}",
|
||||
profile_cfg.server_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_DEFAULT_UNIX_SHELL_PATH={}",
|
||||
profile_cfg.default_unix_shell_path
|
||||
"cargo:rustc-env=KANIDM_CLIENT_CONFIG_PATH={}",
|
||||
profile_cfg.client_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_RESOLVER_CONFIG_PATH={}",
|
||||
profile_cfg.resolver_config_path
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=KANIDM_RESOLVER_UNIX_SHELL_PATH={}",
|
||||
profile_cfg.resolver_unix_shell_path
|
||||
);
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ mod tests {
|
|||
|
||||
struct TestBVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for TestBVisitor {
|
||||
impl Visitor<'_> for TestBVisitor {
|
||||
type Value = TestB;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
|
|
|
@ -17,9 +17,8 @@ test = false
|
|||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
gethostname = "0.5.0"
|
||||
num_enum = { workspace = true }
|
||||
opentelemetry = { workspace = true, features = ["metrics", "rt-tokio"] }
|
||||
opentelemetry = { workspace = true, features = ["metrics"] }
|
||||
opentelemetry-otlp = { workspace = true, default-features = false, features = [
|
||||
"serde",
|
||||
"logs",
|
||||
|
@ -27,9 +26,12 @@ opentelemetry-otlp = { workspace = true, default-features = false, features = [
|
|||
"http-proto",
|
||||
"grpc-tonic",
|
||||
] }
|
||||
opentelemetry_sdk = { workspace = true }
|
||||
opentelemetry_sdk = { workspace = true, features = ["rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = { workspace = true }
|
||||
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
tracing = { workspace = true, features = ["attributes"] }
|
||||
tracing-core = { workspace = true }
|
||||
tracing-forest = { workspace = true, features = [
|
||||
"uuid",
|
||||
"smallvec",
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
use gethostname::gethostname;
|
||||
use opentelemetry::KeyValue;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
use opentelemetry_otlp::{Protocol, WithExportConfig};
|
||||
use opentelemetry_sdk::trace::{self, Sampler};
|
||||
use opentelemetry_sdk::Resource;
|
||||
use std::time::Duration;
|
||||
|
||||
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
|
||||
|
||||
use opentelemetry_sdk::{
|
||||
trace::{Sampler, TracerProvider},
|
||||
Resource,
|
||||
};
|
||||
use tracing::Subscriber;
|
||||
use tracing_subscriber::Registry;
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
use tracing_core::Level;
|
||||
|
||||
use tracing_subscriber::{filter::Directive, prelude::*, EnvFilter, Registry};
|
||||
|
||||
pub const MAX_EVENTS_PER_SPAN: u32 = 64 * 1024;
|
||||
pub const MAX_ATTRIBUTES_PER_SPAN: u32 = 128;
|
||||
|
||||
use opentelemetry_semantic_conventions::{
|
||||
attribute::{SERVICE_NAME, SERVICE_VERSION},
|
||||
SCHEMA_URL,
|
||||
};
|
||||
|
||||
// TODO: this is coming back later
|
||||
// #[allow(dead_code)]
|
||||
// pub fn init_metrics() -> metrics::Result<MeterProvider> {
|
||||
|
@ -44,28 +54,26 @@ pub fn start_logging_pipeline(
|
|||
// adding these filters because when you close out the process the OTLP comms layer is NOISY
|
||||
let forest_filter = forest_filter
|
||||
.add_directive(
|
||||
"tonic=info"
|
||||
.parse()
|
||||
.expect("Failed to set tonic logging to info"),
|
||||
Directive::from_str("tonic=info").expect("Failed to set tonic logging to info"),
|
||||
)
|
||||
.add_directive("h2=info".parse().expect("Failed to set h2 logging to info"))
|
||||
.add_directive(
|
||||
"hyper=info"
|
||||
.parse()
|
||||
.expect("Failed to set hyper logging to info"),
|
||||
Directive::from_str("h2=info").expect("Failed to set h2 logging to info"),
|
||||
)
|
||||
.add_directive(
|
||||
Directive::from_str("hyper=info").expect("Failed to set hyper logging to info"),
|
||||
);
|
||||
let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
|
||||
let t_filter: EnvFilter = EnvFilter::builder()
|
||||
.with_default_directive(log_filter.into())
|
||||
.from_env_lossy();
|
||||
|
||||
let tracer = opentelemetry_otlp::new_pipeline().tracing().with_exporter(
|
||||
opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
.with_endpoint(endpoint)
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.with_protocol(Protocol::HttpBinary),
|
||||
);
|
||||
let otlp_exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_endpoint(endpoint)
|
||||
.with_protocol(Protocol::HttpBinary)
|
||||
.with_timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
// this env var gets set at build time, if we can pull it, add it to the metadata
|
||||
let git_rev = match option_env!("KANIDM_PKG_COMMIT_REV") {
|
||||
|
@ -74,39 +82,47 @@ pub fn start_logging_pipeline(
|
|||
};
|
||||
|
||||
let version = format!("{}{}", env!("CARGO_PKG_VERSION"), git_rev);
|
||||
let hostname = gethostname();
|
||||
let hostname = hostname.to_string_lossy();
|
||||
let hostname = hostname.to_lowercase();
|
||||
// let hostname = gethostname::gethostname();
|
||||
// let hostname = hostname.to_string_lossy();
|
||||
// let hostname = hostname.to_lowercase();
|
||||
|
||||
let tracer = tracer
|
||||
.with_trace_config(
|
||||
trace::config()
|
||||
// we want *everything!*
|
||||
.with_sampler(Sampler::AlwaysOn)
|
||||
.with_max_events_per_span(MAX_EVENTS_PER_SPAN)
|
||||
.with_max_attributes_per_span(MAX_ATTRIBUTES_PER_SPAN)
|
||||
.with_resource(Resource::new(vec![
|
||||
KeyValue::new("service.name", service_name),
|
||||
KeyValue::new("service.version", version),
|
||||
KeyValue::new("host.name", hostname),
|
||||
// TODO: it'd be really nice to be able to set the instance ID here, from the server UUID so we know *which* instance on this host is logging
|
||||
])),
|
||||
let resource = Resource::from_schema_url(
|
||||
[
|
||||
// TODO: it'd be really nice to be able to set the instance ID here, from the server UUID so we know *which* instance on this host is logging
|
||||
KeyValue::new(SERVICE_NAME, service_name),
|
||||
KeyValue::new(SERVICE_VERSION, version),
|
||||
// TODO: currently marked as an experimental flag, leaving it out for now
|
||||
// KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, hostname),
|
||||
],
|
||||
SCHEMA_URL,
|
||||
);
|
||||
|
||||
let provider = TracerProvider::builder()
|
||||
.with_batch_exporter(otlp_exporter, opentelemetry_sdk::runtime::Tokio)
|
||||
// we want *everything!*
|
||||
.with_sampler(Sampler::AlwaysOn)
|
||||
.with_max_events_per_span(MAX_EVENTS_PER_SPAN)
|
||||
.with_max_attributes_per_span(MAX_ATTRIBUTES_PER_SPAN)
|
||||
.with_resource(resource)
|
||||
.build();
|
||||
|
||||
global::set_tracer_provider(provider.clone());
|
||||
provider.tracer("tracing-otel-subscriber");
|
||||
use tracing_opentelemetry::OpenTelemetryLayer;
|
||||
|
||||
let registry = tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::filter::LevelFilter::from_level(Level::INFO)
|
||||
.with_filter(t_filter),
|
||||
)
|
||||
.install_batch(opentelemetry::runtime::Tokio)
|
||||
.map_err(|err| {
|
||||
let err = format!("Failed to start OTLP pipeline: {:?}", err);
|
||||
eprintln!("{}", err);
|
||||
err
|
||||
})?;
|
||||
// Create a tracing layer with the configured tracer;
|
||||
let telemetry = tracing_opentelemetry::layer()
|
||||
.with_tracer(tracer)
|
||||
.with_threads(true)
|
||||
.with_filter(t_filter);
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
// .with(MetricsLayer::new(meter_provider.clone()))
|
||||
.with(forest_layer)
|
||||
.with(OpenTelemetryLayer::new(
|
||||
provider.tracer("tracing-otel-subscriber"),
|
||||
));
|
||||
|
||||
Ok(Box::new(
|
||||
Registry::default().with(forest_layer).with(telemetry),
|
||||
))
|
||||
Ok(Box::new(registry))
|
||||
}
|
||||
None => {
|
||||
let forest_layer = tracing_forest::ForestLayer::default().with_filter(forest_filter);
|
||||
|
@ -122,7 +138,6 @@ pub struct TracingPipelineGuard {}
|
|||
impl Drop for TracingPipelineGuard {
|
||||
fn drop(&mut self) {
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
opentelemetry::global::shutdown_logger_provider();
|
||||
eprintln!("Logging pipeline completed shutdown");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "kanidm_utils_users"
|
||||
description = "Kanidm utility crate"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
rust-version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
version = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
rust-version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[lib]
|
||||
test = true
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 942c7b69ca807cc38186b63ab02a391bac9eac7e
|
||||
Subproject commit 8d7579fb543632df74e609892c69ce9f368fdd02
|
4
platform/freebsd/README.md
Normal file
4
platform/freebsd/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
These are the FreeBSD port Makefiles and other supporting files. In the future we will submit
|
||||
these to FreeBSD ports rather than maintaining them here.
|
||||
|
||||
|
49
platform/freebsd/client/Makefile
Normal file
49
platform/freebsd/client/Makefile
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
PORTNAME= kanidm
|
||||
# DISTVERSION= 1.5.0-dev
|
||||
# DISTVERSIONPREFIX= v
|
||||
|
||||
DISTVERSION= g20250102
|
||||
GH_TAGNAME= edb8cccc84e9dacd2ac31ea1162dd24c0c454c55
|
||||
GH_ACCOUNT= Firstyear
|
||||
|
||||
CATEGORIES= security net databases
|
||||
|
||||
LICENSE= MPL20
|
||||
LICENSE_FILE= ${WRKSRC}/LICENSE.md
|
||||
MAINTAINER= william@blackhats.net.au
|
||||
COMMENT= Simple and secure identity management platform
|
||||
WWW= https://github.com/kanidm/kanidm/
|
||||
|
||||
USES= cargo ssl
|
||||
USE_GITHUB= yes
|
||||
|
||||
ONLY_FOR_ARCHS= aarch64 amd64
|
||||
|
||||
CARGO_ENV= KANIDM_BUILD_PROFILE=release_freebsd
|
||||
|
||||
CARGO_BUILD_ARGS = -p kanidm_tools -p kanidm_unix_int -p nss_kanidm -p pam_kanidm
|
||||
|
||||
CARGO_INSTALL= no
|
||||
|
||||
USE_RC_SUBR= kanidm_unixd kanidm_unixd_tasks
|
||||
|
||||
USERS= _kanidm_unixd
|
||||
GROUPS= _kanidm_unixd
|
||||
|
||||
do-install:
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm-unix ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_ssh_authorizedkeys ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_ssh_authorizedkeys_direct ${STAGEDIR}${PREFIX}/bin
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_unixd ${STAGEDIR}${PREFIX}/libexec
|
||||
${INSTALL_PROGRAM} ${WRKDIR}/target/release/kanidm_unixd_tasks ${STAGEDIR}${PREFIX}/libexec
|
||||
${INSTALL_LIB} ${WRKDIR}/target/release/libnss_kanidm.so ${STAGEDIR}${PREFIX}/lib/nss_kanidm.so.1
|
||||
${INSTALL_LIB} ${WRKDIR}/target/release/libpam_kanidm.so ${STAGEDIR}${PREFIX}/lib
|
||||
${MKDIR} ${STAGEDIR}${PREFIX}/etc
|
||||
${MKDIR} ${STAGEDIR}${PREFIX}/etc/kanidm
|
||||
${MKDIR} ${STAGEDIR}/var/run/kanidm-unixd
|
||||
${MKDIR} ${STAGEDIR}/var/lib/kanidm-unixd
|
||||
${MKDIR} ${STAGEDIR}/var/cache/kanidm-unixd
|
||||
|
||||
.include <bsd.port.mk>
|
27
platform/freebsd/client/files/kanidm_unixd.in
Normal file
27
platform/freebsd/client/files/kanidm_unixd.in
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# PROVIDE: kanidm_unixd
|
||||
# REQUIRE: LOGIN
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# kanidm_unixd_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable kanidm_unixd.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=kanidm_unixd
|
||||
rcvar=kanidm_unixd_enable
|
||||
|
||||
load_rc_config $name
|
||||
|
||||
: ${kanidm_unixd_enable:="NO"}
|
||||
|
||||
pidfile="/var/run/kanidm-unixd.pid"
|
||||
command=/usr/sbin/daemon
|
||||
command_args="-u _kanidm_unixd -p /var/run/kanidm-unixd.pid -T kanidm_unixd /usr/local/libexec/${name}"
|
||||
procname=/usr/local/libexec/${name}
|
||||
|
||||
run_rc_command "$1"
|
27
platform/freebsd/client/files/kanidm_unixd_tasks.in
Normal file
27
platform/freebsd/client/files/kanidm_unixd_tasks.in
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# PROVIDE: kanidm_unixd_tasks
|
||||
# REQUIRE: LOGIN
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add these lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# kanidm_unixd_tasks_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable kanidm_unixd_tasks.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=kanidm_unixd_tasks
|
||||
rcvar=kanidm_unixd_tasks_enable
|
||||
|
||||
load_rc_config $name
|
||||
|
||||
: ${kanidm_unixd_tasks_enable:="NO"}
|
||||
|
||||
pidfile="/var/run/kanidm-unixd-tasks.pid"
|
||||
command=/usr/sbin/daemon
|
||||
command_args="-u root -p /var/run/kanidm-unixd-tasks.pid -T kanidm_unixd_tasks /usr/local/libexec/${name}"
|
||||
procname=/usr/local/libexec/${name}
|
||||
|
||||
run_rc_command "$1"
|
1
platform/freebsd/client/pkg-descr
Normal file
1
platform/freebsd/client/pkg-descr
Normal file
|
@ -0,0 +1 @@
|
|||
Kanidm is a simple and secure identity provider and client for UNIX systems
|
13
platform/freebsd/client/pkg-plist
Normal file
13
platform/freebsd/client/pkg-plist
Normal file
|
@ -0,0 +1,13 @@
|
|||
bin/kanidm
|
||||
bin/kanidm-unix
|
||||
bin/kanidm_ssh_authorizedkeys
|
||||
bin/kanidm_ssh_authorizedkeys_direct
|
||||
lib/nss_kanidm.so.1
|
||||
lib/libpam_kanidm.so
|
||||
libexec/kanidm_unixd
|
||||
libexec/kanidm_unixd_tasks
|
||||
@dir %%ETCDIR%%
|
||||
@dir /var/lib
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,750) /var/cache/kanidm-unixd
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,750) /var/lib/kanidm-unixd
|
||||
@dir(_kanidm_unixd,_kanidm_unixd,755) /var/run/kanidm-unixd
|
|
@ -12,7 +12,7 @@ Conflicts=nscd.service
|
|||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
SupplementaryGroups=tss shadow
|
||||
SupplementaryGroups=tss
|
||||
UMask=0027
|
||||
CacheDirectory=kanidm-unixd
|
||||
RuntimeDirectory=kanidm-unixd
|
||||
|
|
|
@ -42,3 +42,6 @@ sshkeys = { workspace = true }
|
|||
[dev-dependencies]
|
||||
enum-iterator = { workspace = true }
|
||||
serde_urlencoded = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
kanidm_build_profiles = { workspace = true }
|
||||
|
|
3
proto/build.rs
Normal file
3
proto/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
profiles::apply_profile();
|
||||
}
|
|
@ -22,6 +22,8 @@ pub enum Attribute {
|
|||
AcpCreateClass,
|
||||
AcpEnable,
|
||||
AcpModifyClass,
|
||||
AcpModifyPresentClass,
|
||||
AcpModifyRemoveClass,
|
||||
AcpModifyPresentAttr,
|
||||
AcpModifyRemovedAttr,
|
||||
AcpReceiver,
|
||||
|
@ -53,6 +55,7 @@ pub enum Attribute {
|
|||
DisplayName,
|
||||
Dn,
|
||||
Domain,
|
||||
DomainAllowEasterEggs,
|
||||
DomainDevelopmentTaint,
|
||||
DomainDisplayName,
|
||||
DomainLdapBasedn,
|
||||
|
@ -79,6 +82,7 @@ pub enum Attribute {
|
|||
IdVerificationEcKey,
|
||||
Image,
|
||||
Index,
|
||||
Indexed,
|
||||
IpaNtHash,
|
||||
IpaSshPubKey,
|
||||
JwsEs256PrivateKey,
|
||||
|
@ -93,6 +97,7 @@ pub enum Attribute {
|
|||
LdapEmailAddress,
|
||||
/// An LDAP Compatible sshkeys virtual attribute
|
||||
LdapKeys,
|
||||
LdapMaxQueryableAttrs,
|
||||
LegalName,
|
||||
LimitSearchMaxResults,
|
||||
LimitSearchMaxFilterTest,
|
||||
|
@ -140,6 +145,9 @@ pub enum Attribute {
|
|||
Refers,
|
||||
Replicated,
|
||||
Rs256PrivateKeyDer,
|
||||
/// A set of scim schemas. This is similar to a kanidm class.
|
||||
#[serde(rename = "schemas")]
|
||||
ScimSchemas,
|
||||
Scope,
|
||||
SourceUuid,
|
||||
Spn,
|
||||
|
@ -249,6 +257,8 @@ impl Attribute {
|
|||
Attribute::AcpCreateClass => ATTR_ACP_CREATE_CLASS,
|
||||
Attribute::AcpEnable => ATTR_ACP_ENABLE,
|
||||
Attribute::AcpModifyClass => ATTR_ACP_MODIFY_CLASS,
|
||||
Attribute::AcpModifyPresentClass => ATTR_ACP_MODIFY_PRESENT_CLASS,
|
||||
Attribute::AcpModifyRemoveClass => ATTR_ACP_MODIFY_REMOVE_CLASS,
|
||||
Attribute::AcpModifyPresentAttr => ATTR_ACP_MODIFY_PRESENTATTR,
|
||||
Attribute::AcpModifyRemovedAttr => ATTR_ACP_MODIFY_REMOVEDATTR,
|
||||
Attribute::AcpReceiver => ATTR_ACP_RECEIVER,
|
||||
|
@ -279,6 +289,7 @@ impl Attribute {
|
|||
Attribute::DisplayName => ATTR_DISPLAYNAME,
|
||||
Attribute::Dn => ATTR_DN,
|
||||
Attribute::Domain => ATTR_DOMAIN,
|
||||
Attribute::DomainAllowEasterEggs => ATTR_DOMAIN_ALLOW_EASTER_EGGS,
|
||||
Attribute::DomainDevelopmentTaint => ATTR_DOMAIN_DEVELOPMENT_TAINT,
|
||||
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
|
||||
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
|
||||
|
@ -305,6 +316,7 @@ impl Attribute {
|
|||
Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY,
|
||||
Attribute::Image => ATTR_IMAGE,
|
||||
Attribute::Index => ATTR_INDEX,
|
||||
Attribute::Indexed => ATTR_INDEXED,
|
||||
Attribute::IpaNtHash => ATTR_IPANTHASH,
|
||||
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
|
||||
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
|
||||
|
@ -317,6 +329,7 @@ impl Attribute {
|
|||
Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND,
|
||||
Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS,
|
||||
Attribute::LdapKeys => ATTR_LDAP_KEYS,
|
||||
Attribute::LdapMaxQueryableAttrs => ATTR_LDAP_MAX_QUERYABLE_ATTRS,
|
||||
Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY,
|
||||
Attribute::LegalName => ATTR_LEGALNAME,
|
||||
Attribute::LimitSearchMaxResults => ATTR_LIMIT_SEARCH_MAX_RESULTS,
|
||||
|
@ -368,6 +381,7 @@ impl Attribute {
|
|||
Attribute::Replicated => ATTR_REPLICATED,
|
||||
Attribute::Rs256PrivateKeyDer => ATTR_RS256_PRIVATE_KEY_DER,
|
||||
Attribute::Scope => ATTR_SCOPE,
|
||||
Attribute::ScimSchemas => ATTR_SCIM_SCHEMAS,
|
||||
Attribute::SourceUuid => ATTR_SOURCE_UUID,
|
||||
Attribute::Spn => ATTR_SPN,
|
||||
Attribute::SshPublicKey => ATTR_SSH_PUBLICKEY,
|
||||
|
@ -430,6 +444,8 @@ impl Attribute {
|
|||
ATTR_ACP_CREATE_CLASS => Attribute::AcpCreateClass,
|
||||
ATTR_ACP_ENABLE => Attribute::AcpEnable,
|
||||
ATTR_ACP_MODIFY_CLASS => Attribute::AcpModifyClass,
|
||||
ATTR_ACP_MODIFY_PRESENT_CLASS => Attribute::AcpModifyPresentClass,
|
||||
ATTR_ACP_MODIFY_REMOVE_CLASS => Attribute::AcpModifyRemoveClass,
|
||||
ATTR_ACP_MODIFY_PRESENTATTR => Attribute::AcpModifyPresentAttr,
|
||||
ATTR_ACP_MODIFY_REMOVEDATTR => Attribute::AcpModifyRemovedAttr,
|
||||
ATTR_ACP_RECEIVER => Attribute::AcpReceiver,
|
||||
|
@ -460,6 +476,7 @@ impl Attribute {
|
|||
ATTR_DISPLAYNAME => Attribute::DisplayName,
|
||||
ATTR_DN => Attribute::Dn,
|
||||
ATTR_DOMAIN => Attribute::Domain,
|
||||
ATTR_DOMAIN_ALLOW_EASTER_EGGS => Attribute::DomainAllowEasterEggs,
|
||||
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
|
||||
ATTR_DOMAIN_DEVELOPMENT_TAINT => Attribute::DomainDevelopmentTaint,
|
||||
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,
|
||||
|
@ -486,6 +503,7 @@ impl Attribute {
|
|||
ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey,
|
||||
ATTR_IMAGE => Attribute::Image,
|
||||
ATTR_INDEX => Attribute::Index,
|
||||
ATTR_INDEXED => Attribute::Indexed,
|
||||
ATTR_IPANTHASH => Attribute::IpaNtHash,
|
||||
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
|
||||
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
|
||||
|
@ -498,6 +516,7 @@ impl Attribute {
|
|||
ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind,
|
||||
ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress,
|
||||
ATTR_LDAP_KEYS => Attribute::LdapKeys,
|
||||
ATTR_LDAP_MAX_QUERYABLE_ATTRS => Attribute::LdapMaxQueryableAttrs,
|
||||
ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey,
|
||||
ATTR_LEGALNAME => Attribute::LegalName,
|
||||
ATTR_LINKEDGROUP => Attribute::LinkedGroup,
|
||||
|
@ -548,6 +567,7 @@ impl Attribute {
|
|||
ATTR_REFERS => Attribute::Refers,
|
||||
ATTR_REPLICATED => Attribute::Replicated,
|
||||
ATTR_RS256_PRIVATE_KEY_DER => Attribute::Rs256PrivateKeyDer,
|
||||
ATTR_SCIM_SCHEMAS => Attribute::ScimSchemas,
|
||||
ATTR_SCOPE => Attribute::Scope,
|
||||
ATTR_SOURCE_UUID => Attribute::SourceUuid,
|
||||
ATTR_SPN => Attribute::Spn,
|
||||
|
@ -620,6 +640,71 @@ impl From<Attribute> for String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sub attributes are a component of SCIM, allowing tagged sub properties of a complex
|
||||
/// attribute to be accessed.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "lowercase", try_from = "&str", into = "AttrString")]
|
||||
pub enum SubAttribute {
|
||||
/// Denotes a primary value.
|
||||
Primary,
|
||||
|
||||
#[cfg(not(test))]
|
||||
Custom(AttrString),
|
||||
}
|
||||
|
||||
impl From<SubAttribute> for AttrString {
|
||||
fn from(val: SubAttribute) -> Self {
|
||||
AttrString::from(val.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SubAttribute {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::inner_from_str(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SubAttribute {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self::inner_from_str(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAttribute {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
SubAttribute::Primary => SUB_ATTR_PRIMARY,
|
||||
#[cfg(not(test))]
|
||||
SubAttribute::Custom(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
// We allow this because the standard lib from_str is fallible, and we want an infallible version.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
fn inner_from_str(value: &str) -> Self {
|
||||
// Could this be something like heapless to save allocations? Also gives a way
|
||||
// to limit length of str?
|
||||
match value.to_lowercase().as_str() {
|
||||
SUB_ATTR_PRIMARY => SubAttribute::Primary,
|
||||
|
||||
#[cfg(not(test))]
|
||||
_ => SubAttribute::Custom(AttrString::from(value)),
|
||||
|
||||
// Allowed only in tests
|
||||
#[allow(clippy::unreachable)]
|
||||
#[cfg(test)]
|
||||
_ => {
|
||||
unreachable!(
|
||||
"Check that you've implemented the SubAttribute conversion for {:?}",
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Attribute;
|
||||
|
|
|
@ -30,7 +30,7 @@ pub const VALID_IMAGE_UPLOAD_CONTENT_TYPES: [&str; 5] = [
|
|||
pub const APPLICATION_JSON: &str = "application/json";
|
||||
|
||||
/// The "system" path for Kanidm client config
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH: &str = "/etc/kanidm/config";
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH: &str = env!("KANIDM_CLIENT_CONFIG_PATH");
|
||||
/// The user-owned path for Kanidm client config
|
||||
pub const DEFAULT_CLIENT_CONFIG_PATH_HOME: &str = "~/.config/kanidm";
|
||||
|
||||
|
@ -39,6 +39,8 @@ pub const DEFAULT_SERVER_ADDRESS: &str = "127.0.0.1:8443";
|
|||
pub const DEFAULT_SERVER_LOCALHOST: &str = "localhost:8443";
|
||||
/// The default LDAP bind address for the Kanidm client
|
||||
pub const DEFAULT_LDAP_LOCALHOST: &str = "localhost:636";
|
||||
/// The default amount of attributes that can be queried in LDAP
|
||||
pub const DEFAULT_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES: usize = 16;
|
||||
/// Default replication configuration
|
||||
pub const DEFAULT_REPLICATION_ADDRESS: &str = "127.0.0.1:8444";
|
||||
pub const DEFAULT_REPLICATION_ORIGIN: &str = "repl://localhost:8444";
|
||||
|
@ -60,6 +62,8 @@ pub const ATTR_ACP_CREATE_ATTR: &str = "acp_create_attr";
|
|||
pub const ATTR_ACP_CREATE_CLASS: &str = "acp_create_class";
|
||||
pub const ATTR_ACP_ENABLE: &str = "acp_enable";
|
||||
pub const ATTR_ACP_MODIFY_CLASS: &str = "acp_modify_class";
|
||||
pub const ATTR_ACP_MODIFY_PRESENT_CLASS: &str = "acp_modify_present_class";
|
||||
pub const ATTR_ACP_MODIFY_REMOVE_CLASS: &str = "acp_modify_remove_class";
|
||||
pub const ATTR_ACP_MODIFY_PRESENTATTR: &str = "acp_modify_presentattr";
|
||||
pub const ATTR_ACP_MODIFY_REMOVEDATTR: &str = "acp_modify_removedattr";
|
||||
pub const ATTR_ACP_RECEIVER_GROUP: &str = "acp_receiver_group";
|
||||
|
@ -89,6 +93,7 @@ pub const ATTR_DESCRIPTION: &str = "description";
|
|||
pub const ATTR_DIRECTMEMBEROF: &str = "directmemberof";
|
||||
pub const ATTR_DISPLAYNAME: &str = "displayname";
|
||||
pub const ATTR_DN: &str = "dn";
|
||||
pub const ATTR_DOMAIN_ALLOW_EASTER_EGGS: &str = "domain_allow_easter_eggs";
|
||||
pub const ATTR_DOMAIN_DEVELOPMENT_TAINT: &str = "domain_development_taint";
|
||||
pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name";
|
||||
pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn";
|
||||
|
@ -101,6 +106,7 @@ pub const ATTR_DYNGROUP_FILTER: &str = "dyngroup_filter";
|
|||
pub const ATTR_DYNGROUP: &str = "dyngroup";
|
||||
pub const ATTR_DYNMEMBER: &str = "dynmember";
|
||||
pub const ATTR_LDAP_EMAIL_ADDRESS: &str = "emailaddress";
|
||||
pub const ATTR_LDAP_MAX_QUERYABLE_ATTRS: &str = "ldap_max_queryable_attrs";
|
||||
pub const ATTR_EMAIL_ALTERNATIVE: &str = "emailalternative";
|
||||
pub const ATTR_EMAIL_PRIMARY: &str = "emailprimary";
|
||||
pub const ATTR_EMAIL: &str = "email";
|
||||
|
@ -120,6 +126,7 @@ pub const ATTR_GROUP: &str = "group";
|
|||
pub const ATTR_ID_VERIFICATION_ECKEY: &str = "id_verification_eckey";
|
||||
pub const ATTR_IMAGE: &str = "image";
|
||||
pub const ATTR_INDEX: &str = "index";
|
||||
pub const ATTR_INDEXED: &str = "indexed";
|
||||
pub const ATTR_IPANTHASH: &str = "ipanthash";
|
||||
pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey";
|
||||
pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key";
|
||||
|
@ -179,6 +186,7 @@ pub const ATTR_RECYCLEDDIRECTMEMBEROF: &str = "recycled_directmemberof";
|
|||
pub const ATTR_REFERS: &str = "refers";
|
||||
pub const ATTR_REPLICATED: &str = "replicated";
|
||||
pub const ATTR_RS256_PRIVATE_KEY_DER: &str = "rs256_private_key_der";
|
||||
pub const ATTR_SCIM_SCHEMAS: &str = "schemas";
|
||||
pub const ATTR_SCOPE: &str = "scope";
|
||||
pub const ATTR_SELF: &str = "self";
|
||||
pub const ATTR_SOURCE_UUID: &str = "source_uuid";
|
||||
|
@ -215,8 +223,11 @@ pub const ATTR_VERSION: &str = "version";
|
|||
pub const ATTR_WEBAUTHN_ATTESTATION_CA_LIST: &str = "webauthn_attestation_ca_list";
|
||||
pub const ATTR_ALLOW_PRIMARY_CRED_FALLBACK: &str = "allow_primary_cred_fallback";
|
||||
|
||||
pub const SUB_ATTR_PRIMARY: &str = "primary";
|
||||
|
||||
pub const OAUTH2_SCOPE_EMAIL: &str = ATTR_EMAIL;
|
||||
pub const OAUTH2_SCOPE_GROUPS: &str = "groups";
|
||||
pub const OAUTH2_SCOPE_SSH_PUBLICKEYS: &str = "ssh_publickeys";
|
||||
pub const OAUTH2_SCOPE_OPENID: &str = "openid";
|
||||
pub const OAUTH2_SCOPE_READ: &str = "read";
|
||||
pub const OAUTH2_SCOPE_SUPPLEMENT: &str = "supplement";
|
||||
|
|
|
@ -130,6 +130,7 @@ pub enum CURegState {
|
|||
None,
|
||||
TotpCheck(TotpSecret),
|
||||
TotpTryAgain,
|
||||
TotpNameTryAgain(String),
|
||||
TotpInvalidSha1,
|
||||
BackupCodes(Vec<String>),
|
||||
Passkey(CreationChallengeResponse),
|
||||
|
@ -160,6 +161,7 @@ pub enum CURegWarning {
|
|||
AttestedResidentKeyRequired,
|
||||
Unsatisfiable,
|
||||
WebauthnAttestationUnsatisfiable,
|
||||
WebauthnUserVerificationRequired,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
|
|
|
@ -142,17 +142,32 @@ pub enum OperationError {
|
|||
DatabaseLockAcquisitionTimeout,
|
||||
|
||||
// Specific internal errors.
|
||||
AU0001InvalidState,
|
||||
AU0002JwsSerialisation,
|
||||
AU0003JwsSignature,
|
||||
AU0004UserAuthTokenInvalid,
|
||||
AU0005DelayedProcessFailure,
|
||||
AU0006CredentialMayNotReauthenticate,
|
||||
AU0007UserAuthTokenInvalid,
|
||||
|
||||
// Kanidm Generic Errors
|
||||
KG001TaskTimeout,
|
||||
KG002TaskCommFailure,
|
||||
KG003CacheClearFailed,
|
||||
|
||||
// What about something like this for unique errors?
|
||||
// Credential Update Errors
|
||||
CU0001WebauthnAttestationNotTrusted,
|
||||
CU0002WebauthnRegistrationError,
|
||||
CU0003WebauthnUserNotVerified,
|
||||
|
||||
// The session is inconsistent and can't be committed, but the errors
|
||||
// can be resolved.
|
||||
CU0004SessionInconsistent,
|
||||
// Another session used this intent token, and so it can't be committed.
|
||||
CU0005IntentTokenConflict,
|
||||
// The intent token was invalidated before we could commit.
|
||||
CU0006IntentTokenInvalidated,
|
||||
|
||||
// ValueSet errors
|
||||
VS0001IncomingReplSshPublicKey,
|
||||
VS0002CertificatePublicKeyDigest,
|
||||
|
@ -173,6 +188,31 @@ pub enum OperationError {
|
|||
|
||||
// SCIM
|
||||
SC0001IncomingSshPublicKey,
|
||||
SC0002ReferenceSyntaxInvalid,
|
||||
SC0003MailSyntaxInvalid,
|
||||
SC0004UuidSyntaxInvalid,
|
||||
SC0005BoolSyntaxInvalid,
|
||||
SC0006Uint32SyntaxInvalid,
|
||||
SC0007UrlSyntaxInvalid,
|
||||
SC0008SyntaxTypeSyntaxInvalid,
|
||||
SC0009IndexTypeSyntaxInvalid,
|
||||
SC0010DateTimeSyntaxInvalid,
|
||||
SC0011AddressSyntaxInvalid,
|
||||
SC0012CertificateSyntaxInvalid,
|
||||
SC0013CertificateInvalidDer,
|
||||
SC0014CertificateInvalidDigest,
|
||||
SC0015CredentialTypeSyntaxInvalid,
|
||||
SC0016InameSyntaxInvalid,
|
||||
SC0017Iutf8SyntaxInvalid,
|
||||
SC0018NsUniqueIdSyntaxInvalid,
|
||||
SC0019Oauth2ScopeSyntaxInvalid,
|
||||
SC0020Oauth2ScopeMapSyntaxInvalid,
|
||||
SC0021Oauth2ScopeMapMissingGroupIdentifier,
|
||||
SC0022Oauth2ClaimMapSyntaxInvalid,
|
||||
SC0023Oauth2ClaimMapMissingGroupIdentifier,
|
||||
SC0024SshPublicKeySyntaxInvalid,
|
||||
SC0025UiHintSyntaxInvalid,
|
||||
SC0026Utf8SyntaxInvalid,
|
||||
// Migration
|
||||
MG0001InvalidReMigrationLevel,
|
||||
MG0002RaiseDomainLevelExceedsMaximum,
|
||||
|
@ -182,6 +222,7 @@ pub enum OperationError {
|
|||
MG0006SKConstraintsNotMet,
|
||||
MG0007Oauth2StrictConstraintsNotMet,
|
||||
MG0008SkipUpgradeAttempted,
|
||||
MG0009InvalidTargetLevelForBootstrap,
|
||||
//
|
||||
KP0001KeyProviderNotLoaded,
|
||||
KP0002KeyProviderInvalidClass,
|
||||
|
@ -235,6 +276,7 @@ pub enum OperationError {
|
|||
// Web UI
|
||||
UI0001ChallengeSerialisation,
|
||||
UI0002InvalidState,
|
||||
UI0003InvalidOauth2Resume,
|
||||
|
||||
// Unixd Things
|
||||
KU001InitWhileSessionActive,
|
||||
|
@ -271,7 +313,7 @@ impl Display for OperationError {
|
|||
|
||||
impl OperationError {
|
||||
/// Return the message associated with the error if there is one.
|
||||
fn message(&self) -> Option<String> {
|
||||
pub fn message(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::SessionExpired => None,
|
||||
Self::EmptyRequest => None,
|
||||
|
@ -337,9 +379,23 @@ impl OperationError {
|
|||
Self::TransactionAlreadyCommitted => None,
|
||||
Self::ValueDenyName => None,
|
||||
Self::DatabaseLockAcquisitionTimeout => Some("Unable to acquire a database lock - the current server may be too busy. Try again later.".into()),
|
||||
|
||||
Self::AU0001InvalidState => Some("Invalid authentication session state for request".into()),
|
||||
Self::AU0002JwsSerialisation => Some("JWS serialisation failed".into()),
|
||||
Self::AU0003JwsSignature => Some("JWS signature failed".into()),
|
||||
Self::AU0004UserAuthTokenInvalid => Some("User auth token was unable to be generated".into()),
|
||||
Self::AU0005DelayedProcessFailure => Some("Delaying processing failure, unable to proceed".into()),
|
||||
Self::AU0006CredentialMayNotReauthenticate => Some("Credential may not reauthenticate".into()),
|
||||
Self::AU0007UserAuthTokenInvalid => Some("User auth token was unable to be generated".into()),
|
||||
|
||||
Self::CU0001WebauthnAttestationNotTrusted => None,
|
||||
Self::CU0002WebauthnRegistrationError => None,
|
||||
Self::CU0003WebauthnUserNotVerified => Some("User Verification bit not set while registering credential, you may need to configure a PIN on this device.".into()),
|
||||
|
||||
Self::CU0004SessionInconsistent => Some("The session is unable to be committed due to unresolved warnings.".into()),
|
||||
Self::CU0005IntentTokenConflict => Some("The intent token used to create this session has been reused in another browser/tab and may not proceed.".into()),
|
||||
Self::CU0006IntentTokenInvalidated => Some("The intent token has been invalidated/revoked before the commit could be accepted. Has it been used in another browser or tab?".into()),
|
||||
|
||||
Self::DB0001MismatchedRestoreVersion => None,
|
||||
Self::DB0002MismatchedRestoreVersion => None,
|
||||
Self::DB0003FilterResolveCacheBuild => None,
|
||||
|
@ -407,10 +463,39 @@ impl OperationError {
|
|||
Self::MG0006SKConstraintsNotMet => Some("Migration Constraints Not Met - Security Keys should not be present.".into()),
|
||||
Self::MG0007Oauth2StrictConstraintsNotMet => Some("Migration Constraints Not Met - All OAuth2 clients must have strict-redirect-uri mode enabled.".into()),
|
||||
Self::MG0008SkipUpgradeAttempted => Some("Skip Upgrade Attempted.".into()),
|
||||
Self::MG0009InvalidTargetLevelForBootstrap => Some("The request target domain level was not valid for bootstrapping a new server instance".into()),
|
||||
Self::PL0001GidOverlapsSystemRange => None,
|
||||
Self::SC0001IncomingSshPublicKey => None,
|
||||
Self::SC0002ReferenceSyntaxInvalid => Some("A SCIM Reference Set contained invalid syntax and can not be processed.".into()),
|
||||
Self::SC0003MailSyntaxInvalid => Some("A SCIM Mail Address contained invalid syntax".into()),
|
||||
Self::SC0004UuidSyntaxInvalid => Some("A SCIM Uuid contained invalid syntax".into()),
|
||||
Self::SC0005BoolSyntaxInvalid => Some("A SCIM boolean contained invalid syntax".into()),
|
||||
Self::SC0006Uint32SyntaxInvalid => Some("A SCIM Uint32 contained invalid syntax".into()),
|
||||
Self::SC0007UrlSyntaxInvalid => Some("A SCIM Url contained invalid syntax".into()),
|
||||
Self::SC0008SyntaxTypeSyntaxInvalid => Some("A SCIM SyntaxType contained invalid syntax".into()),
|
||||
Self::SC0009IndexTypeSyntaxInvalid => Some("A SCIM IndexType contained invalid syntax".into()),
|
||||
Self::SC0010DateTimeSyntaxInvalid => Some("A SCIM DateTime contained invalid syntax".into()),
|
||||
|
||||
Self::SC0011AddressSyntaxInvalid => Some("A SCIM Address contained invalid syntax".into()),
|
||||
Self::SC0012CertificateSyntaxInvalid => Some("A SCIM Certificate contained invalid binary data".into()),
|
||||
Self::SC0013CertificateInvalidDer => Some("A SCIM Certificate did not contain valid DER".into()),
|
||||
Self::SC0014CertificateInvalidDigest => Some("A SCIM Certificate was unable to be digested".into()),
|
||||
Self::SC0015CredentialTypeSyntaxInvalid => Some("A SCIM CredentialType contained invalid syntax".into()),
|
||||
Self::SC0016InameSyntaxInvalid => Some("A SCIM Iname string contained invalid syntax".into()),
|
||||
Self::SC0017Iutf8SyntaxInvalid => Some("A SCIM Iutf8 string contained invalid syntax".into()),
|
||||
Self::SC0018NsUniqueIdSyntaxInvalid => Some("A SCIM NsUniqueID contained invalid syntax".into()),
|
||||
Self::SC0019Oauth2ScopeSyntaxInvalid => Some("A SCIM Oauth2 Scope contained invalid syntax".into()),
|
||||
Self::SC0020Oauth2ScopeMapSyntaxInvalid => Some("A SCIM Oauth2 Scope Map contained invalid syntax".into()),
|
||||
Self::SC0021Oauth2ScopeMapMissingGroupIdentifier => Some("A SCIM Oauth2 Scope Map was missing a group name or uuid".into()),
|
||||
Self::SC0022Oauth2ClaimMapSyntaxInvalid => Some("A SCIM Oauth2 Claim Map contained invalid syntax".into()),
|
||||
Self::SC0023Oauth2ClaimMapMissingGroupIdentifier => Some("A SCIM Claim Map was missing a group name or uuid".into()),
|
||||
Self::SC0024SshPublicKeySyntaxInvalid => Some("A SCIM Ssh Public Key contained invalid syntax".into()),
|
||||
Self::SC0025UiHintSyntaxInvalid => Some("A SCIM UiHint contained invalid syntax".into()),
|
||||
Self::SC0026Utf8SyntaxInvalid => Some("A SCIM Utf8 String Scope Map contained invalid syntax".into()),
|
||||
|
||||
Self::UI0001ChallengeSerialisation => Some("The WebAuthn challenge was unable to be serialised.".into()),
|
||||
Self::UI0002InvalidState => Some("The credential update process returned an invalid state transition.".into()),
|
||||
Self::UI0003InvalidOauth2Resume => Some("The server attemped to resume OAuth2, but no OAuth2 session is in progress.".into()),
|
||||
Self::VL0001ValueSshPublicKeyString => None,
|
||||
Self::VS0001IncomingReplSshPublicKey => None,
|
||||
Self::VS0002CertificatePublicKeyDigest |
|
||||
|
|
|
@ -6,7 +6,10 @@ use base64::{engine::general_purpose::STANDARD, Engine as _};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::base64::{Base64, UrlSafe};
|
||||
use serde_with::formats::SpaceSeparator;
|
||||
use serde_with::{formats, serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use serde_with::{
|
||||
formats, rust::deserialize_ignore_any, serde_as, skip_serializing_none, NoneAsEmptyString,
|
||||
StringWithSeparator,
|
||||
};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -33,26 +36,73 @@ pub struct PkceRequest {
|
|||
|
||||
/// An OAuth2 client redirects to the authorisation server with Authorisation Request
|
||||
/// parameters.
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct AuthorisationRequest {
|
||||
// Must be "code". (or token, see 4.2.1)
|
||||
pub response_type: String,
|
||||
pub response_type: ResponseType,
|
||||
/// Response mode.
|
||||
///
|
||||
/// Optional; defaults to `query` for `response_type=code` (Auth Code), and
|
||||
/// `fragment` for `response_type=token` (Implicit Grant, which we probably
|
||||
/// won't support).
|
||||
///
|
||||
/// Reference:
|
||||
/// [OAuth 2.0 Multiple Response Type Encoding Practices: Response Modes](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes)
|
||||
pub response_mode: Option<ResponseMode>,
|
||||
pub client_id: String,
|
||||
pub state: String,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub state: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub pkce_request: Option<PkceRequest>,
|
||||
pub redirect_uri: Url,
|
||||
pub scope: String,
|
||||
#[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
|
||||
pub scope: BTreeSet<String>,
|
||||
// OIDC adds a nonce parameter that is optional.
|
||||
pub nonce: Option<String>,
|
||||
// OIDC also allows other optional params
|
||||
#[serde(flatten)]
|
||||
pub oidc_ext: AuthorisationRequestOidc,
|
||||
// Needs to be hoisted here due to serde flatten bug #3185
|
||||
pub max_age: Option<i64>,
|
||||
#[serde(flatten)]
|
||||
pub unknown_keys: BTreeMap<String, serde_json::value::Value>,
|
||||
}
|
||||
|
||||
impl AuthorisationRequest {
|
||||
/// Get the `response_mode` appropriate for this request, taking into
|
||||
/// account defaults from the `response_type` parameter.
|
||||
///
|
||||
/// Returns `None` if the selection is invalid.
|
||||
///
|
||||
/// Reference:
|
||||
/// [OAuth 2.0 Multiple Response Type Encoding Practices: Response Modes](https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes)
|
||||
pub const fn get_response_mode(&self) -> Option<ResponseMode> {
|
||||
match (self.response_mode, self.response_type) {
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token
|
||||
// The default Response Mode for this Response Type is the fragment
|
||||
// encoding and the query encoding MUST NOT be used.
|
||||
(None, ResponseType::IdToken) => Some(ResponseMode::Fragment),
|
||||
(Some(ResponseMode::Query), ResponseType::IdToken) => None,
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2
|
||||
(None, ResponseType::Code) => Some(ResponseMode::Query),
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
|
||||
(None, ResponseType::Token) => Some(ResponseMode::Fragment),
|
||||
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Security
|
||||
// In no case should a set of Authorization Response parameters
|
||||
// whose default Response Mode is the fragment encoding be encoded
|
||||
// using the query encoding.
|
||||
(Some(ResponseMode::Query), ResponseType::Token) => None,
|
||||
|
||||
// Allow others.
|
||||
(Some(m), _) => Some(m),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An OIDC client redirects to the authorisation server with Authorisation Request
|
||||
/// parameters.
|
||||
#[skip_serializing_none]
|
||||
|
@ -60,7 +110,6 @@ pub struct AuthorisationRequest {
|
|||
pub struct AuthorisationRequestOidc {
|
||||
pub display: Option<String>,
|
||||
pub prompt: Option<String>,
|
||||
pub max_age: Option<i64>,
|
||||
pub ui_locales: Option<()>,
|
||||
pub claims_locales: Option<()>,
|
||||
pub id_token_hint: Option<String>,
|
||||
|
@ -184,6 +233,7 @@ pub struct OAuth2RFC9068TokenExtensions {
|
|||
}
|
||||
|
||||
/// The response for an access token
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AccessTokenResponse {
|
||||
|
@ -194,7 +244,8 @@ pub struct AccessTokenResponse {
|
|||
pub refresh_token: Option<String>,
|
||||
/// Space separated list of scopes that were approved, if this differs from the
|
||||
/// original request.
|
||||
pub scope: Option<String>,
|
||||
#[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
|
||||
pub scope: BTreeSet<String>,
|
||||
/// If the `openid` scope was requested, an `id_token` may be present in the response.
|
||||
pub id_token: Option<String>,
|
||||
}
|
||||
|
@ -247,11 +298,13 @@ pub struct AccessTokenIntrospectRequest {
|
|||
|
||||
/// Response to an introspection request. If the token is inactive or revoked, only
|
||||
/// `active` will be set to the value of `false`.
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct AccessTokenIntrospectResponse {
|
||||
pub active: bool,
|
||||
pub scope: Option<String>,
|
||||
#[serde_as(as = "StringWithSeparator::<SpaceSeparator, String>")]
|
||||
pub scope: BTreeSet<String>,
|
||||
pub client_id: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub token_type: Option<AccessTokenType>,
|
||||
|
@ -268,7 +321,7 @@ impl AccessTokenIntrospectResponse {
|
|||
pub fn inactive() -> Self {
|
||||
AccessTokenIntrospectResponse {
|
||||
active: false,
|
||||
scope: None,
|
||||
scope: BTreeSet::default(),
|
||||
client_id: None,
|
||||
username: None,
|
||||
token_type: None,
|
||||
|
@ -283,19 +336,27 @@ impl AccessTokenIntrospectResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseType {
|
||||
// Auth Code flow
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
|
||||
Code,
|
||||
// Implicit Grant flow
|
||||
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
|
||||
Token,
|
||||
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#id_token
|
||||
IdToken,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ResponseMode {
|
||||
Query,
|
||||
Fragment,
|
||||
FormPost,
|
||||
#[serde(other, deserialize_with = "deserialize_ignore_any")]
|
||||
Invalid,
|
||||
}
|
||||
|
||||
fn response_modes_supported_default() -> Vec<ResponseMode> {
|
||||
|
@ -386,6 +447,21 @@ fn require_request_uri_parameter_supported_default() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerRel {
|
||||
pub rel: String,
|
||||
pub href: String,
|
||||
}
|
||||
|
||||
/// The response to an Webfinger request. Only a subset of the body is defined here.
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc7033#section-4.4>
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OidcWebfingerResponse {
|
||||
pub subject: String,
|
||||
pub links: Vec<OidcWebfingerRel>,
|
||||
}
|
||||
|
||||
/// The response to an OpenID connect discovery request
|
||||
/// <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
|
||||
#[skip_serializing_none]
|
||||
|
|
|
@ -1,11 +1,192 @@
|
|||
//! These are types that a client will send to the server.
|
||||
use super::ScimEntryGetQuery;
|
||||
use super::ScimOauth2ClaimMapJoinChar;
|
||||
use crate::attribute::{Attribute, SubAttribute};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_with::formats::PreferMany;
|
||||
use serde_with::OneOrMany;
|
||||
use serde_with::{base64, formats, serde_as, skip_serializing_none};
|
||||
use sshkey_attest::proto::PublicKey as SshPublicKey;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub type ScimSshPublicKeys = Vec<ScimSshPublicKey>;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ScimSshPublicKey {
|
||||
pub label: String,
|
||||
pub value: SshPublicKey,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ScimReference {
|
||||
pub uuid: Option<Uuid>,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
pub type ScimReferences = Vec<ScimReference>;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(transparent)]
|
||||
pub struct ScimDateTime {
|
||||
#[serde_as(as = "Rfc3339")]
|
||||
pub date_time: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimCertificate {
|
||||
#[serde_as(as = "base64::Base64<base64::UrlSafe, formats::Unpadded>")]
|
||||
pub der: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAddress {
|
||||
pub street_address: String,
|
||||
pub locality: String,
|
||||
pub region: String,
|
||||
pub postal_code: String,
|
||||
pub country: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ClaimMap {
|
||||
pub group: Option<String>,
|
||||
pub group_uuid: Option<Uuid>,
|
||||
pub claim: String,
|
||||
pub join_char: ScimOauth2ClaimMapJoinChar,
|
||||
pub values: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ScopeMap {
|
||||
pub group: Option<String>,
|
||||
pub group_uuid: Option<Uuid>,
|
||||
pub scopes: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ScimEntryPutKanidm {
|
||||
pub id: Uuid,
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, Option<super::server::ScimValueKanidm>>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct ScimStrings(#[serde_as(as = "OneOrMany<_, PreferMany>")] pub Vec<String>);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct ScimEntryPutGeneric {
|
||||
// id is only used to target the entry in question
|
||||
pub id: Uuid,
|
||||
|
||||
#[serde(flatten)]
|
||||
/// Non-standard extension - allow query options to be set in a put request. This
|
||||
/// is because a put request also returns the entry state post put, so we want
|
||||
/// to allow putters to adjust and control what is returned here.
|
||||
pub query: ScimEntryGetQuery,
|
||||
|
||||
// external_id can't be set by put
|
||||
// meta is skipped on put
|
||||
// Schemas are decoded as part of "attrs".
|
||||
/// Update an attribute to contain the following value state.
|
||||
/// If the attribute is None, it is removed.
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, Option<JsonValue>>,
|
||||
}
|
||||
|
||||
impl TryFrom<ScimEntryPutKanidm> for ScimEntryPutGeneric {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: ScimEntryPutKanidm) -> Result<Self, Self::Error> {
|
||||
let ScimEntryPutKanidm { id, attrs } = value;
|
||||
|
||||
let attrs = attrs
|
||||
.into_iter()
|
||||
.map(|(attr, value)| {
|
||||
if let Some(v) = value {
|
||||
serde_json::to_value(v).map(|json_value| (attr, Some(json_value)))
|
||||
} else {
|
||||
Ok((attr, None))
|
||||
}
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(ScimEntryPutGeneric {
|
||||
id,
|
||||
attrs,
|
||||
query: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct AttrPath {
|
||||
pub a: Attribute,
|
||||
pub s: Option<SubAttribute>,
|
||||
}
|
||||
|
||||
impl From<Attribute> for AttrPath {
|
||||
fn from(a: Attribute) -> Self {
|
||||
Self { a, s: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Attribute, SubAttribute)> for AttrPath {
|
||||
fn from((a, s): (Attribute, SubAttribute)) -> Self {
|
||||
Self { a, s: Some(s) }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub enum ScimFilter {
|
||||
Or(Box<ScimFilter>, Box<ScimFilter>),
|
||||
And(Box<ScimFilter>, Box<ScimFilter>),
|
||||
Not(Box<ScimFilter>),
|
||||
|
||||
Present(AttrPath),
|
||||
Equal(AttrPath, JsonValue),
|
||||
NotEqual(AttrPath, JsonValue),
|
||||
Contains(AttrPath, JsonValue),
|
||||
StartsWith(AttrPath, JsonValue),
|
||||
EndsWith(AttrPath, JsonValue),
|
||||
Greater(AttrPath, JsonValue),
|
||||
Less(AttrPath, JsonValue),
|
||||
GreaterOrEqual(AttrPath, JsonValue),
|
||||
LessOrEqual(AttrPath, JsonValue),
|
||||
|
||||
Complex(Attribute, Box<ScimComplexFilter>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
pub enum ScimComplexFilter {
|
||||
Or(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
|
||||
And(Box<ScimComplexFilter>, Box<ScimComplexFilter>),
|
||||
Not(Box<ScimComplexFilter>),
|
||||
|
||||
Present(SubAttribute),
|
||||
Equal(SubAttribute, JsonValue),
|
||||
NotEqual(SubAttribute, JsonValue),
|
||||
Contains(SubAttribute, JsonValue),
|
||||
StartsWith(SubAttribute, JsonValue),
|
||||
EndsWith(SubAttribute, JsonValue),
|
||||
Greater(SubAttribute, JsonValue),
|
||||
Less(SubAttribute, JsonValue),
|
||||
GreaterOrEqual(SubAttribute, JsonValue),
|
||||
LessOrEqual(SubAttribute, JsonValue),
|
||||
}
|
||||
|
|
|
@ -11,15 +11,16 @@
|
|||
//! The [scim_proto] library, which is generic over all scim implementations.
|
||||
//!
|
||||
//! The client module, which describes how a client should transmit entries, and
|
||||
//! how it should parse them when it recieves them.
|
||||
//! how it should parse them when it receives them.
|
||||
//!
|
||||
//! The server module, which describes how a server should transmit entries and
|
||||
//! how it should recieve them.
|
||||
//! how it should receive them.
|
||||
|
||||
use crate::attribute::Attribute;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use sshkey_attest::proto::PublicKey as SshPublicKey;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Not;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use serde_with::formats::CommaSeparator;
|
||||
|
@ -27,6 +28,7 @@ use serde_with::{serde_as, skip_serializing_none, StringWithSeparator};
|
|||
|
||||
pub use self::synch::*;
|
||||
pub use scim_proto::prelude::*;
|
||||
pub use serde_json::Value as JsonValue;
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
|
@ -46,10 +48,52 @@ pub struct ScimEntryGeneric {
|
|||
/// SCIM Query Parameters used during the get of a single entry
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||
pub struct ScimEntryGetQuery {
|
||||
#[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, Attribute>>")]
|
||||
pub attributes: Option<Vec<Attribute>>,
|
||||
#[serde(default, skip_serializing_if = "<&bool>::not")]
|
||||
pub ext_access_check: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
|
||||
pub enum ScimSchema {
|
||||
#[serde(rename = "urn:ietf:params:scim:schemas:kanidm:sync:1:account")]
|
||||
SyncAccountV1,
|
||||
#[serde(rename = "urn:ietf:params:scim:schemas:kanidm:sync:1:group")]
|
||||
SyncV1GroupV1,
|
||||
#[serde(rename = "urn:ietf:params:scim:schemas:kanidm:sync:1:person")]
|
||||
SyncV1PersonV1,
|
||||
#[serde(rename = "urn:ietf:params:scim:schemas:kanidm:sync:1:posixaccount")]
|
||||
SyncV1PosixAccountV1,
|
||||
#[serde(rename = "urn:ietf:params:scim:schemas:kanidm:sync:1:posixgroup")]
|
||||
SyncV1PosixGroupV1,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ScimMail {
|
||||
#[serde(default)]
|
||||
pub primary: bool,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimSshPublicKey {
|
||||
pub label: String,
|
||||
pub value: SshPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, ToSchema)]
|
||||
pub enum ScimOauth2ClaimMapJoinChar {
|
||||
#[serde(rename = ",", alias = "csv")]
|
||||
CommaSeparatedValue,
|
||||
#[serde(rename = " ", alias = "ssv")]
|
||||
SpaceSeparatedValue,
|
||||
#[serde(rename = ";", alias = "json_array")]
|
||||
JsonArray,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -79,12 +123,15 @@ mod tests {
|
|||
// Group
|
||||
let group_uuid = uuid::uuid!("2d0a9e7c-cc08-4ca2-8d7f-114f9abcfc8a");
|
||||
|
||||
let group = ScimSyncGroup::builder("testgroup".to_string(), group_uuid)
|
||||
.set_description(Some("test desc".to_string()))
|
||||
.set_gidnumber(Some(12345))
|
||||
.set_members(vec!["member_a".to_string(), "member_a".to_string()].into_iter())
|
||||
.set_external_id(Some("cn=testgroup".to_string()))
|
||||
.build();
|
||||
let group = ScimSyncGroup::builder(
|
||||
group_uuid,
|
||||
"cn=testgroup".to_string(),
|
||||
"testgroup".to_string(),
|
||||
)
|
||||
.set_description(Some("test desc".to_string()))
|
||||
.set_gidnumber(Some(12345))
|
||||
.set_members(vec!["member_a".to_string(), "member_a".to_string()].into_iter())
|
||||
.build();
|
||||
|
||||
let entry: Result<ScimEntry, _> = group.try_into();
|
||||
|
||||
|
@ -95,32 +142,35 @@ mod tests {
|
|||
|
||||
let user_sshkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey";
|
||||
|
||||
let person =
|
||||
ScimSyncPerson::builder(user_uuid, "testuser".to_string(), "Test User".to_string())
|
||||
.set_password_import(Some("new_password".to_string()))
|
||||
.set_unix_password_import(Some("new_password".to_string()))
|
||||
.set_totp_import(vec![ScimTotp {
|
||||
external_id: "Totp".to_string(),
|
||||
secret: "abcd".to_string(),
|
||||
algo: "SHA3".to_string(),
|
||||
step: 60,
|
||||
digits: 8,
|
||||
}])
|
||||
.set_mail(vec![MultiValueAttr {
|
||||
primary: Some(true),
|
||||
value: "testuser@example.com".to_string(),
|
||||
..Default::default()
|
||||
}])
|
||||
.set_ssh_publickey(vec![ScimSshPubKey {
|
||||
label: "Key McKeyface".to_string(),
|
||||
value: user_sshkey.to_string(),
|
||||
}])
|
||||
.set_login_shell(Some("/bin/false".to_string()))
|
||||
.set_account_valid_from(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_account_expire(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_gidnumber(Some(54321))
|
||||
.set_external_id(Some("cn=testuser".to_string()))
|
||||
.build();
|
||||
let person = ScimSyncPerson::builder(
|
||||
user_uuid,
|
||||
"cn=testuser".to_string(),
|
||||
"testuser".to_string(),
|
||||
"Test User".to_string(),
|
||||
)
|
||||
.set_password_import(Some("new_password".to_string()))
|
||||
.set_unix_password_import(Some("new_password".to_string()))
|
||||
.set_totp_import(vec![ScimTotp {
|
||||
external_id: "Totp".to_string(),
|
||||
secret: "abcd".to_string(),
|
||||
algo: "SHA3".to_string(),
|
||||
step: 60,
|
||||
digits: 8,
|
||||
}])
|
||||
.set_mail(vec![MultiValueAttr {
|
||||
primary: Some(true),
|
||||
value: "testuser@example.com".to_string(),
|
||||
..Default::default()
|
||||
}])
|
||||
.set_ssh_publickey(vec![ScimSshPubKey {
|
||||
label: "Key McKeyface".to_string(),
|
||||
value: user_sshkey.to_string(),
|
||||
}])
|
||||
.set_login_shell(Some("/bin/false".to_string()))
|
||||
.set_account_valid_from(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_account_expire(Some("2023-11-28T04:57:55Z".to_string()))
|
||||
.set_gidnumber(Some(54321))
|
||||
.build();
|
||||
|
||||
let entry: Result<ScimEntry, _> = person.try_into();
|
||||
|
||||
|
@ -131,7 +181,10 @@ mod tests {
|
|||
fn scim_entry_get_query() {
|
||||
use super::*;
|
||||
|
||||
let q = ScimEntryGetQuery { attributes: None };
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: None,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
|
||||
|
@ -139,6 +192,7 @@ mod tests {
|
|||
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: Some(vec![Attribute::Name]),
|
||||
ext_access_check: false,
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
|
@ -146,9 +200,10 @@ mod tests {
|
|||
|
||||
let q = ScimEntryGetQuery {
|
||||
attributes: Some(vec![Attribute::Name, Attribute::Spn]),
|
||||
ext_access_check: true,
|
||||
};
|
||||
|
||||
let txt = serde_urlencoded::to_string(&q).unwrap();
|
||||
assert_eq!(txt, "attributes=name%2Cspn");
|
||||
assert_eq!(txt, "attributes=name%2Cspn&ext_access_check=true");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use super::ScimMail;
|
||||
use super::ScimOauth2ClaimMapJoinChar;
|
||||
use super::ScimSshPublicKey;
|
||||
use crate::attribute::Attribute;
|
||||
use crate::internal::UiHint;
|
||||
use scim_proto::ScimEntryHeader;
|
||||
use serde::Serialize;
|
||||
use serde_with::{base64, formats, hex::Hex, serde_as, skip_serializing_none, StringWithSeparator};
|
||||
use sshkey_attest::proto::PublicKey as SshPublicKey;
|
||||
use serde_with::{base64, formats, hex::Hex, serde_as, skip_serializing_none};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
|
@ -13,14 +16,54 @@ use uuid::Uuid;
|
|||
/// A strongly typed ScimEntry that is for transmission to clients. This uses
|
||||
/// Kanidm internal strong types for values allowing direct serialisation and
|
||||
/// transmission.
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimEntryKanidm {
|
||||
#[serde(flatten)]
|
||||
pub header: ScimEntryHeader,
|
||||
|
||||
pub ext_access_check: Option<ScimEffectiveAccess>,
|
||||
#[serde(flatten)]
|
||||
pub attrs: BTreeMap<Attribute, ScimValueKanidm>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub enum ScimAttributeEffectiveAccess {
|
||||
/// All attributes on the entry have this permission granted
|
||||
Grant,
|
||||
/// All attributes on the entry have this permission denied
|
||||
Deny,
|
||||
/// The following attributes on the entry have this permission granted
|
||||
Allow(BTreeSet<Attribute>),
|
||||
}
|
||||
|
||||
impl ScimAttributeEffectiveAccess {
|
||||
/// Check if the effective access allows or denies this attribute
|
||||
pub fn check(&self, attr: &Attribute) -> bool {
|
||||
match self {
|
||||
Self::Grant => true,
|
||||
Self::Deny => false,
|
||||
Self::Allow(set) => set.contains(attr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimEffectiveAccess {
|
||||
/// The identity that inherits the effective permission
|
||||
pub ident: Uuid,
|
||||
/// If the ident may delete the target entry
|
||||
pub delete: bool,
|
||||
/// The set of effective access over search events
|
||||
pub search: ScimAttributeEffectiveAccess,
|
||||
/// The set of effective access over modify present events
|
||||
pub modify_present: ScimAttributeEffectiveAccess,
|
||||
/// The set of effective access over modify remove events
|
||||
pub modify_remove: ScimAttributeEffectiveAccess,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimAddress {
|
||||
|
@ -32,13 +75,6 @@ pub struct ScimAddress {
|
|||
pub country: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimMail {
|
||||
pub primary: bool,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimApplicationPassword {
|
||||
|
@ -75,13 +111,6 @@ pub struct ScimAuditString {
|
|||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimSshPublicKey {
|
||||
pub label: String,
|
||||
pub value: SshPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ScimIntentTokenState {
|
||||
|
@ -164,8 +193,8 @@ pub struct ScimApiToken {
|
|||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ScopeMap {
|
||||
pub uuid: Uuid,
|
||||
#[serde_as(as = "StringWithSeparator::<formats::SpaceSeparator, String>")]
|
||||
pub group: String,
|
||||
pub group_uuid: Uuid,
|
||||
pub scopes: BTreeSet<String>,
|
||||
}
|
||||
|
||||
|
@ -173,14 +202,13 @@ pub struct ScimOAuth2ScopeMap {
|
|||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimOAuth2ClaimMap {
|
||||
pub group: Uuid,
|
||||
pub group: String,
|
||||
pub group_uuid: Uuid,
|
||||
pub claim: String,
|
||||
pub join_char: String,
|
||||
#[serde_as(as = "StringWithSeparator::<formats::SpaceSeparator, String>")]
|
||||
pub join_char: ScimOauth2ClaimMapJoinChar,
|
||||
pub values: BTreeSet<String>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, PartialEq, Eq, ToSchema)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ScimReference {
|
||||
|
@ -226,6 +254,99 @@ pub enum ScimValueKanidm {
|
|||
OAuth2ScopeMap(Vec<ScimOAuth2ScopeMap>),
|
||||
OAuth2ClaimMap(Vec<ScimOAuth2ClaimMap>),
|
||||
KeyInternal(Vec<ScimKeyInternal>),
|
||||
UiHints(Vec<UiHint>),
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Debug, Clone, ToSchema)]
|
||||
pub struct ScimPerson {
|
||||
pub uuid: Uuid,
|
||||
pub name: String,
|
||||
pub displayname: String,
|
||||
pub spn: String,
|
||||
pub description: Option<String>,
|
||||
pub mails: Vec<ScimMail>,
|
||||
pub managed_by: Option<ScimReference>,
|
||||
pub groups: Vec<ScimReference>,
|
||||
}
|
||||
|
||||
impl TryFrom<ScimEntryKanidm> for ScimPerson {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(scim_entry: ScimEntryKanidm) -> Result<Self, Self::Error> {
|
||||
let uuid = scim_entry.header.id;
|
||||
let name = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::Name)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or(())?;
|
||||
|
||||
let displayname = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::DisplayName)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or(())?;
|
||||
|
||||
let spn = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::Spn)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.ok_or(())?;
|
||||
|
||||
let description = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::Description)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let mails = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::Mail)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::Mail(m) => Some(m.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let groups = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::DirectMemberOf)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::EntryReferences(v) => Some(v.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let managed_by = scim_entry
|
||||
.attrs
|
||||
.get(&Attribute::EntryManagedBy)
|
||||
.and_then(|v| match v {
|
||||
ScimValueKanidm::EntryReference(v) => Some(v.clone()),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
Ok(ScimPerson {
|
||||
uuid,
|
||||
name,
|
||||
displayname,
|
||||
spn,
|
||||
description,
|
||||
mails,
|
||||
managed_by,
|
||||
groups,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for ScimValueKanidm {
|
||||
|
@ -240,6 +361,12 @@ impl From<OffsetDateTime> for ScimValueKanidm {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Vec<UiHint>> for ScimValueKanidm {
|
||||
fn from(set: Vec<UiHint>) -> Self {
|
||||
Self::UiHints(set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<OffsetDateTime>> for ScimValueKanidm {
|
||||
fn from(set: Vec<OffsetDateTime>) -> Self {
|
||||
Self::ArrayDateTime(set)
|
||||
|
@ -252,6 +379,12 @@ impl From<String> for ScimValueKanidm {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ScimValueKanidm {
|
||||
fn from(s: &str) -> Self {
|
||||
Self::String(s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<String>> for ScimValueKanidm {
|
||||
fn from(set: Vec<String>) -> Self {
|
||||
Self::ArrayString(set)
|
||||
|
|
|
@ -85,19 +85,19 @@ pub struct ScimSshPubKey {
|
|||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ScimSyncPerson {
|
||||
#[serde(flatten)]
|
||||
pub entry: ScimEntryHeader,
|
||||
|
||||
pub user_name: String,
|
||||
pub display_name: String,
|
||||
pub name: String,
|
||||
pub displayname: String,
|
||||
pub gidnumber: Option<u32>,
|
||||
pub password_import: Option<String>,
|
||||
pub unix_password_import: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub totp_import: Vec<ScimTotp>,
|
||||
pub login_shell: Option<String>,
|
||||
pub loginshell: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub mail: Vec<MultiValueAttr>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
|
@ -119,7 +119,12 @@ pub struct ScimSyncPersonBuilder {
|
|||
}
|
||||
|
||||
impl ScimSyncPerson {
|
||||
pub fn builder(id: Uuid, user_name: String, display_name: String) -> ScimSyncPersonBuilder {
|
||||
pub fn builder(
|
||||
id: Uuid,
|
||||
external_id: String,
|
||||
name: String,
|
||||
displayname: String,
|
||||
) -> ScimSyncPersonBuilder {
|
||||
ScimSyncPersonBuilder {
|
||||
inner: ScimSyncPerson {
|
||||
entry: ScimEntryHeader {
|
||||
|
@ -128,16 +133,16 @@ impl ScimSyncPerson {
|
|||
SCIM_SCHEMA_SYNC_PERSON.to_string(),
|
||||
],
|
||||
id,
|
||||
external_id: None,
|
||||
external_id: Some(external_id),
|
||||
meta: None,
|
||||
},
|
||||
user_name,
|
||||
display_name,
|
||||
name,
|
||||
displayname,
|
||||
gidnumber: None,
|
||||
password_import: None,
|
||||
unix_password_import: None,
|
||||
totp_import: Vec::with_capacity(0),
|
||||
login_shell: None,
|
||||
loginshell: None,
|
||||
mail: Vec::with_capacity(0),
|
||||
ssh_publickey: Vec::with_capacity(0),
|
||||
account_valid_from: None,
|
||||
|
@ -173,8 +178,8 @@ impl ScimSyncPersonBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn set_login_shell(mut self, login_shell: Option<String>) -> Self {
|
||||
self.inner.login_shell = login_shell;
|
||||
pub fn set_login_shell(mut self, loginshell: Option<String>) -> Self {
|
||||
self.inner.loginshell = loginshell;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -205,11 +210,6 @@ impl ScimSyncPersonBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn set_external_id(mut self, external_id: Option<String>) -> Self {
|
||||
self.inner.entry.external_id = external_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ScimSyncPerson {
|
||||
self.inner
|
||||
}
|
||||
|
@ -220,8 +220,9 @@ pub struct ScimExternalMember {
|
|||
pub external_id: String,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ScimSyncGroup {
|
||||
#[serde(flatten)]
|
||||
pub entry: ScimEntryHeader,
|
||||
|
@ -229,7 +230,8 @@ pub struct ScimSyncGroup {
|
|||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub gidnumber: Option<u32>,
|
||||
pub members: Vec<ScimExternalMember>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub member: Vec<ScimExternalMember>,
|
||||
}
|
||||
|
||||
impl TryInto<ScimEntry> for ScimSyncGroup {
|
||||
|
@ -247,19 +249,19 @@ pub struct ScimSyncGroupBuilder {
|
|||
}
|
||||
|
||||
impl ScimSyncGroup {
|
||||
pub fn builder(name: String, id: Uuid) -> ScimSyncGroupBuilder {
|
||||
pub fn builder(id: Uuid, external_id: String, name: String) -> ScimSyncGroupBuilder {
|
||||
ScimSyncGroupBuilder {
|
||||
inner: ScimSyncGroup {
|
||||
entry: ScimEntryHeader {
|
||||
schemas: vec![SCIM_SCHEMA_SYNC_GROUP.to_string()],
|
||||
id,
|
||||
external_id: None,
|
||||
external_id: Some(external_id),
|
||||
meta: None,
|
||||
},
|
||||
name,
|
||||
description: None,
|
||||
gidnumber: None,
|
||||
members: Vec::with_capacity(0),
|
||||
member: Vec::with_capacity(0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -288,17 +290,12 @@ impl ScimSyncGroupBuilder {
|
|||
where
|
||||
I: Iterator<Item = String>,
|
||||
{
|
||||
self.inner.members = member_iter
|
||||
self.inner.member = member_iter
|
||||
.map(|external_id| ScimExternalMember { external_id })
|
||||
.collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_external_id(mut self, external_id: Option<String>) -> Self {
|
||||
self.inner.entry.external_id = external_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> ScimSyncGroup {
|
||||
self.inner
|
||||
}
|
||||
|
|
|
@ -85,10 +85,10 @@ impl fmt::Debug for AuthCredential {
|
|||
pub enum AuthMech {
|
||||
Anonymous,
|
||||
Password,
|
||||
PasswordBackupCode,
|
||||
// Now represents TOTP.
|
||||
#[serde(rename = "passwordmfa")]
|
||||
PasswordTotp,
|
||||
PasswordBackupCode,
|
||||
PasswordSecurityKey,
|
||||
Passkey,
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ pub use self::auth::*;
|
|||
pub use self::unix::*;
|
||||
|
||||
/// The type of Account in use.
|
||||
#[derive(Clone, Copy, Debug, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, ToSchema)]
|
||||
pub enum AccountType {
|
||||
Person,
|
||||
ServiceAccount,
|
||||
|
|
|
@ -17,7 +17,13 @@ import yarl
|
|||
|
||||
from kanidm.models.group import Group, GroupList, IGroup, RawGroup
|
||||
from kanidm.models.oauth2_rs import IOauth2Rs, OAuth2Rs, Oauth2RsList, RawOAuth2Rs
|
||||
from kanidm.models.person import IPerson, Person, PersonList, RawPerson
|
||||
from kanidm.models.person import (
|
||||
IPerson,
|
||||
Person,
|
||||
PersonList,
|
||||
RawPerson,
|
||||
PersonCredentialResetToken,
|
||||
)
|
||||
from kanidm.models.service_account import (
|
||||
IServiceAccount,
|
||||
ServiceAccount,
|
||||
|
@ -93,7 +99,7 @@ class KanidmClient:
|
|||
"""Constructor for KanidmClient"""
|
||||
|
||||
self.logger = logger or getLogger(__name__)
|
||||
self.instance_name = instance_name # TODO: use this in loaders etc
|
||||
self.instance_name = instance_name # TODO: use this in loaders etc
|
||||
if config is not None:
|
||||
self.config = config
|
||||
else:
|
||||
|
@ -123,7 +129,7 @@ class KanidmClient:
|
|||
|
||||
def _configure_ssl(self) -> None:
|
||||
"""Sets up SSL configuration for the client"""
|
||||
if False in [self.config.verify_certificate, self.config.verify_hostnames ]:
|
||||
if False in [self.config.verify_certificate, self.config.verify_hostnames]:
|
||||
logging.debug("Setting up SSL context with no verification")
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
|
||||
self._ssl_context.hostname_checks_common_name = False
|
||||
|
@ -135,9 +141,8 @@ class KanidmClient:
|
|||
raise FileNotFoundError(f"CA Path not found: {self.config.ca_path}")
|
||||
else:
|
||||
self.logger.debug("Setting up SSL context with CA path=%s", self.config.ca_path)
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH,cafile=self.config.ca_path)
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=self.config.ca_path)
|
||||
else:
|
||||
|
||||
logging.debug("Setting up default SSL context")
|
||||
self._ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
|
||||
|
||||
|
@ -521,9 +526,7 @@ class KanidmClient:
|
|||
"""get an OAuth2 client"""
|
||||
endpoint = f"{Endpoints.OAUTH2}/{rs_name}"
|
||||
response: ClientResponse[IOauth2Rs] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get oauth2 resource server: {response.content}")
|
||||
if response.data is None:
|
||||
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
|
||||
|
||||
|
@ -583,9 +586,7 @@ class KanidmClient:
|
|||
"""Get a service account"""
|
||||
endpoint = f"{Endpoints.SERVICE_ACCOUNT}/{name}"
|
||||
response: ClientResponse[IServiceAccount] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get service account: {response.content}")
|
||||
if response.data is None:
|
||||
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
|
||||
|
||||
|
@ -672,9 +673,7 @@ class KanidmClient:
|
|||
"""Get a group"""
|
||||
endpoint = f"{Endpoints.GROUP}/{name}"
|
||||
response: ClientResponse[IGroup] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get group: {response.content}")
|
||||
if response.data is None:
|
||||
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
|
||||
|
||||
|
@ -719,9 +718,7 @@ class KanidmClient:
|
|||
"""Get a person by name"""
|
||||
endpoint = f"{Endpoints.PERSON}/{name}"
|
||||
response: ClientResponse[IPerson] = await self.call_get(endpoint)
|
||||
if response.status_code != 200:
|
||||
raise ValueError(f"Failed to get person: {response.content}")
|
||||
if response.data is None:
|
||||
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
|
||||
|
||||
|
@ -765,6 +762,19 @@ class KanidmClient:
|
|||
endpoint = f"{Endpoints.PERSON}/{id}"
|
||||
return await self.call_delete(endpoint)
|
||||
|
||||
async def person_account_credential_update_token(self, id: str, ttl: Optional[int] = None) -> PersonCredentialResetToken:
|
||||
"""Create a password reset token for person with an optional time to live in seconds"""
|
||||
endpoint = f"{Endpoints.PERSON}/{id}/_credential/_update_intent"
|
||||
if ttl:
|
||||
endpoint = f"{endpoint}/{ttl}"
|
||||
|
||||
response: ClientResponse[Any] = await self.call_get(endpoint)
|
||||
if response.status_code != 200 or response.content is None:
|
||||
raise ValueError(f"Failed to get token: {response.content}")
|
||||
token = PersonCredentialResetToken.model_validate(json_lib.loads(response.content))
|
||||
|
||||
return token
|
||||
|
||||
async def person_account_post_ssh_key(self, id: str, tag: str, pubkey: str) -> ClientResponse[None]:
|
||||
"""Create an SSH key for a user"""
|
||||
endpoint = f"{Endpoints.PERSON}/{id}/_ssh_pubkeys"
|
||||
|
|
|
@ -38,8 +38,15 @@ class RawPerson(BaseModel):
|
|||
uuid=UUID(self.attrs["uuid"][0]),
|
||||
)
|
||||
|
||||
|
||||
PersonList = RootModel[List[RawPerson]]
|
||||
|
||||
|
||||
class IPerson(TypedDict):
|
||||
attrs: Dict[str, List[str]]
|
||||
|
||||
|
||||
class PersonCredentialResetToken(BaseModel):
|
||||
token: str
|
||||
expiry_time: int
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" kanidm RADIUS module """
|
||||
"""kanidm RADIUS module"""
|
||||
|
||||
import asyncio
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from functools import reduce
|
||||
|
@ -16,15 +17,26 @@ from .. import KanidmClient
|
|||
from . import radiusd
|
||||
from .utils import check_vlan
|
||||
|
||||
CONTAINER_CONFIG_FILE_PATH = "/data/radius.toml"
|
||||
|
||||
# the list of places to try
|
||||
CONFIG_PATHS = [
|
||||
os.getenv("KANIDM_RLM_CONFIG", "/data/kanidm"), # container goodness
|
||||
"~/.config/kanidm", # for a user
|
||||
"/etc/kanidm/kanidm", # system-wide
|
||||
"../examples/kanidm", # test mode
|
||||
os.getenv("KANIDM_RLM_CONFIG", CONTAINER_CONFIG_FILE_PATH), # container goodness
|
||||
"~/.config/radius.toml", # for a user
|
||||
"/etc/kanidm/radius.toml", # system-wide
|
||||
"../examples/radius.toml", # test mode
|
||||
"/data/kanidm", # fallback to old path
|
||||
]
|
||||
|
||||
|
||||
def find_radius_config_path() -> Optional[Path]:
|
||||
for config_file_path in CONFIG_PATHS:
|
||||
config_path = Path(config_file_path).expanduser().resolve()
|
||||
if config_path.exists():
|
||||
return config_path
|
||||
return None
|
||||
|
||||
|
||||
def instantiate(_: Any) -> Any:
|
||||
"""start up radiusd"""
|
||||
logging.basicConfig(
|
||||
|
@ -33,16 +45,9 @@ def instantiate(_: Any) -> Any:
|
|||
)
|
||||
logging.info("Starting up!")
|
||||
|
||||
config_path = None
|
||||
for config_file_path in CONFIG_PATHS:
|
||||
config_path = Path(config_file_path).expanduser().resolve()
|
||||
if config_path.exists():
|
||||
break
|
||||
|
||||
if (config_path is None) or (not config_path.exists()):
|
||||
logging.error(
|
||||
"Failed to find configuration file, checked (%s), quitting!", CONFIG_PATHS
|
||||
)
|
||||
config_path = find_radius_config_path()
|
||||
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)
|
||||
|
@ -107,9 +112,7 @@ def authorize(
|
|||
tok = None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
tok = RadiusTokenResponse.model_validate(
|
||||
loop.run_until_complete(_get_radius_token(username=user_id))
|
||||
)
|
||||
tok = RadiusTokenResponse.model_validate(loop.run_until_complete(_get_radius_token(username=user_id)))
|
||||
logging.debug("radius information token: %s", tok)
|
||||
except NoMatchingEntries as error_message:
|
||||
logging.info(
|
||||
|
@ -125,9 +128,7 @@ def authorize(
|
|||
logging.error("kanidm exception: %s, %s", type(error_message), error_message)
|
||||
return radiusd.RLM_MODULE_FAIL
|
||||
if tok is None:
|
||||
logging.info(
|
||||
"kanidm RLM_MODULE_REJECT - unable to retrieve radius information token"
|
||||
)
|
||||
logging.info("kanidm RLM_MODULE_REJECT - unable to retrieve radius information token")
|
||||
return radiusd.RLM_MODULE_REJECT
|
||||
|
||||
# Get values out of the token
|
||||
|
|
|
@ -9,7 +9,7 @@ import logging
|
|||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from authlib.jose import JsonWebSignature # type: ignore
|
||||
from authlib.jose import JsonWebSignature # type: ignore
|
||||
from pydantic import ConfigDict, BaseModel, Field
|
||||
|
||||
from . import TOKEN_PATH
|
||||
|
@ -113,7 +113,7 @@ class ConfigInstance(BaseModel):
|
|||
class TokenStore(BaseModel):
|
||||
"""Represents the user auth tokens, so we can load them from the user store"""
|
||||
|
||||
instances: Dict[str, ConfigInstance] = Field({"" : {}})
|
||||
instances: Dict[str, ConfigInstance] = Field({"": ConfigInstance.model_construct()})
|
||||
|
||||
def save(self, filepath: Path = TOKEN_PATH) -> None:
|
||||
"""saves the cached tokens to disk"""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" type objects """
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
# ^ disabling this because pydantic models don't have public methods
|
||||
|
||||
|
@ -31,7 +32,7 @@ class ClientResponse(BaseModel, Generic[T]):
|
|||
|
||||
|
||||
class AuthInitResponse(BaseModel):
|
||||
"""Aelps parse the response from the Auth 'init' stage"""
|
||||
"""Helps parse the response from the Auth 'init' stage"""
|
||||
|
||||
class _AuthInitState(BaseModel):
|
||||
"""sub-class for the AuthInitResponse model"""
|
||||
|
@ -146,9 +147,7 @@ class RadiusClient(BaseModel):
|
|||
socket.gethostbyname(value)
|
||||
return value
|
||||
except socket.gaierror as error:
|
||||
raise ValueError(
|
||||
f"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}"
|
||||
)
|
||||
raise ValueError(f"ipaddr value ({value}) wasn't an IP Address, Network or valid hostname: {error}")
|
||||
|
||||
|
||||
class KanidmClientConfig(BaseModel):
|
||||
|
@ -172,7 +171,6 @@ class KanidmClientConfig(BaseModel):
|
|||
|
||||
radius_cert_path: str = "/data/cert.pem"
|
||||
radius_key_path: str = "/data/key.pem" # the signing key for radius TLS
|
||||
radius_dh_path: str = "/data/dh.pem" # the diffie-hellman output
|
||||
radius_ca_path: Optional[str] = None
|
||||
radius_ca_dir: Optional[str] = None
|
||||
|
||||
|
@ -196,9 +194,7 @@ class KanidmClientConfig(BaseModel):
|
|||
uri = urlparse(value)
|
||||
valid_schemes = ["http", "https"]
|
||||
if uri.scheme not in valid_schemes:
|
||||
raise ValueError(
|
||||
f"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}"
|
||||
)
|
||||
raise ValueError(f"Invalid URL Scheme for uri='{value}': '{uri.scheme}' - expected one of {valid_schemes}")
|
||||
|
||||
# make sure the URI ends with a /
|
||||
if not value.endswith("/"):
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
""" utility functions """
|
||||
"""utility functions"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Union
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue