Compare commits

...

20 Commits

Author SHA1 Message Date
379ae5e80f
Use compact formatting for tracing if running through cargo
Some checks failed
Build and deploy / Build container and manifests (push) Has been cancelled
2025-03-22 04:54:39 +01:00
77331c5080
Added demo yaml 2025-03-22 04:48:49 +01:00
197b718b85
Add Group controller (#3) 2025-03-22 04:43:57 +01:00
10354ee11a
Added Group resource (#8) 2025-03-22 04:18:38 +01:00
64eac3d7c1
Added create/delete group queries (#8) 2025-03-22 04:18:29 +01:00
2e952ea8cd
Remove async-traits 2025-03-22 04:18:29 +01:00
4308312a61
Fix formatting 2025-03-22 04:18:29 +01:00
b5342ab86a
Update to rust edition 2024 2025-03-22 04:18:29 +01:00
13ddc853fb
Moved ServiceUser into separate file 2025-03-22 04:18:15 +01:00
f085bf1088
Build auditable binaries 2025-03-22 04:18:15 +01:00
0567dea6c5
Add attestations to image 2025-03-22 04:18:15 +01:00
4a589395d2
Add annotations instead of labels to image 2025-03-22 04:18:15 +01:00
99977c1f8d
Set SOURCE_DATE_EPOCH during image build 2025-03-22 04:18:15 +01:00
1b2e0faece
Switched to nonroot distroless base and improved layer caching 2025-03-22 04:18:15 +01:00
16dc78358d
Switch to hadolint for linting Dockerfile 2025-03-22 04:18:15 +01:00
a80e03ac90
Lock rust toolchain version to same version used in Dockerfile 2025-03-22 04:18:14 +01:00
4741fc1f00
Update crates 2025-03-22 04:18:14 +01:00
cc9a8c787f
Run clippy, audit, and test also on Cargo.lock changes 2025-03-22 04:18:14 +01:00
a4deeac442
Add cargo audit 2025-03-22 04:18:14 +01:00
713a6da6e9
Improve workflow and generate CRDs (#3) 2025-03-22 04:17:08 +01:00
24 changed files with 629 additions and 259 deletions

2
.cargo/audit.toml Normal file
View File

@ -0,0 +1,2 @@
[advisories]
ignore = ["RUSTSEC-2024-0344"]

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
*
!queries
!src
!Cargo.*

View File

@ -7,25 +7,19 @@ on:
tags: tags:
- v*.*.* - v*.*.*
env:
OCI_REPO: git.huizinga.dev/dreaded_x/${{ gitea.event.repository.name}}
jobs: jobs:
build: build:
name: Build container name: Build container and manifests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Docker meta - name: Get Git commit timestamps
id: meta run: echo "TIMESTAMP=$(git log -1 --pretty=%ct)" >> $GITHUB_ENV
uses: docker/metadata-action@v5
with:
images: git.huizinga.dev/dreaded_x/${{ gitea.event.repository.name}}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
- name: Login to registry - name: Login to registry
uses: docker/login-action@v3 uses: docker/login-action@v3
@ -34,20 +28,8 @@ jobs:
username: ${{ gitea.actor }} username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }} password: ${{ secrets.REGISTRY_TOKEN }}
- name: Build and push Docker image - name: Set up Docker Buildx
uses: https://github.com/docker/build-push-action@v5 uses: docker/setup-buildx-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
manifests:
name: Publish manifests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install kustomize - name: Install kustomize
run: | run: |
@ -58,26 +40,56 @@ jobs:
with: with:
version: v2.5.0 version: v2.5.0
- name: Login to registry - name: Docker meta
uses: docker/login-action@v3 id: meta
uses: docker/metadata-action@v5
with: with:
registry: git.huizinga.dev images: ${{ env.OCI_REPO }}
username: ${{ gitea.actor }} tags: |
password: ${{ secrets.REGISTRY_TOKEN }} type=edge
type=ref,event=branch
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
# TODO: Generate and include crds - name: Build and export to docker
- name: Generate manifets id: build
uses: docker/build-push-action@v6
with:
context: .
load: true
annotations: ${{ steps.meta.outputs.annotations }}
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
- name: Generate CRDs
run: | run: |
./kustomize build ./manifests | sed "s/\${SHA_SHORT}/$(git rev-parse --short HEAD)/" > ./app.yaml docker run --rm ${{ steps.build.outputs.imageid }} /crdgen > ./manifests/crds.yaml
- name: Push container
uses: docker/build-push-action@v6
id: push
with:
context: .
push: true
sbom: true
provenance: mode=max
tags: ${{ steps.meta.outputs.tags }}
annotations: ${{ steps.meta.outputs.annotations }}
env:
SOURCE_DATE_EPOCH: ${{ env.TIMESTAMP }}
- name: Kustomize manifests
run: |
./kustomize build ./manifests | sed "s/\${DIGEST}/${{ steps.push.outputs.digest }}/" > ./manifests.yaml
- name: Push manifests - name: Push manifests
run: | run: |
flux push artifact oci://git.huizinga.dev/dreaded_x/${{ gitea.event.repository.name }}/manifests:sha-$(git rev-parse --short HEAD) \ flux push artifact oci://$OCI_REPO/manifests:latest \
--path="./app.yaml" \ --path="./manifests.yaml" \
--source="$(git config --get remote.origin.url)" \ --source="$(git config --get remote.origin.url)" \
--revision="$(git branch --show-current)@sha1:$(git rev-parse HEAD)" --revision="$(git rev-parse HEAD)" \
$(echo "${{ steps.meta.outputs.labels }}" | sed -e 's/^/-a /')
- name: Tag manifests flux tag artifact oci://$OCI_REPO/manifests:latest \
run: | $(echo "${{ steps.meta.outputs.tags }}" | sed -e 's/^.*:/--tag /')
flux tag artifact oci://git.huizinga.dev/dreaded_x/${{ gitea.event.repository.name }}/manifests:sha-$(git rev-parse --short HEAD) \
--tag latest

View File

@ -1,9 +1,12 @@
fail_fast: true
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0 rev: v4.6.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml
- id: check-toml - id: check-toml
- id: check-added-large-files - id: check-added-large-files
- id: check-merge-conflict - id: check-merge-conflict
@ -19,10 +22,12 @@ repos:
- id: fmt - id: fmt
name: fmt name: fmt
description: Format files with cargo fmt. description: Format files with cargo fmt.
entry: cargo fmt entry: cargo +nightly fmt
language: system language: system
types: [rust] types: [rust]
args: ["--", "--check"] args: ["--", "--check"]
# For some reason some formatting is different depending on how you invoke?
pass_filenames: false
- id: clippy - id: clippy
name: clippy name: clippy
@ -30,7 +35,17 @@ repos:
entry: cargo clippy entry: cargo clippy
language: system language: system
args: ["--", "-D", "warnings"] args: ["--", "-D", "warnings"]
types: [rust] types: [file]
files: (\.rs|Cargo.lock)$
pass_filenames: false
- id: audit
name: audit
description: Audit packages
entry: cargo audit
language: system
types: [file]
files: (\.rs|Cargo.lock)$
pass_filenames: false pass_filenames: false
- id: test - id: test
@ -39,10 +54,11 @@ repos:
entry: cargo test entry: cargo test
language: system language: system
args: ["--workspace"] args: ["--workspace"]
types: [rust] types: [file]
files: (\.rs|Cargo.lock)$
pass_filenames: false pass_filenames: false
- repo: https://github.com/pryorda/dockerfilelint-precommit-hooks - repo: https://github.com/hadolint/hadolint
rev: v0.1.0 rev: v2.12.0
hooks: hooks:
- id: dockerfilelint - id: hadolint

2
.rustfmt.toml Normal file
View File

@ -0,0 +1,2 @@
imports_granularity = "Module"
group_imports = "StdExternalCrate"

266
Cargo.lock generated
View File

@ -27,7 +27,7 @@ dependencies = [
"getrandom 0.2.15", "getrandom 0.2.15",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy 0.7.35",
] ]
[[package]] [[package]]
@ -115,7 +115,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -126,7 +126,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -137,9 +137,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "backon" name = "backon"
version = "1.4.0" version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49fef586913a57ff189f25c9b3d034356a5bf6b3fa9a7f067588fe1698ba1f5d" checksum = "970d91570c01a8a5959b36ad7dd1c30642df24b6b3068710066f6809f7033bb7"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"gloo-timers", "gloo-timers",
@ -234,15 +234,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.10.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.15" version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -404,7 +404,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.10.0", "strsim 0.10.0",
"syn 2.0.99", "syn 2.0.100",
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
@ -428,7 +428,7 @@ dependencies = [
"cynic-codegen", "cynic-codegen",
"darling", "darling",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -452,7 +452,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim 0.11.1", "strsim 0.11.1",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -463,7 +463,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -483,7 +483,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"unicode-xid", "unicode-xid",
] ]
@ -525,7 +525,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -543,7 +543,7 @@ dependencies = [
"enum-ordinalize", "enum-ordinalize",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -575,7 +575,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -619,9 +619,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]] [[package]]
name = "foldhash" name = "foldhash"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
@ -688,7 +688,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -765,6 +765,20 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "getrandom"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
"wasm-bindgen",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.31.1" version = "0.31.1"
@ -1139,7 +1153,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1342,7 +1356,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1390,9 +1404,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.170" version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
@ -1411,7 +1425,6 @@ name = "lldap-controller"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"chrono", "chrono",
"cynic", "cynic",
"futures", "futures",
@ -1421,7 +1434,7 @@ dependencies = [
"lldap_auth", "lldap_auth",
"passwords", "passwords",
"queries", "queries",
"rand", "rand 0.8.5",
"reqwest", "reqwest",
"schemars", "schemars",
"serde", "serde",
@ -1436,7 +1449,7 @@ dependencies = [
[[package]] [[package]]
name = "lldap_auth" name = "lldap_auth"
version = "0.6.0" version = "0.6.0"
source = "git+https://github.com/lldap/lldap#049e882c359f1480ec3cb492a63a95eaf2efe6d1" source = "git+https://github.com/lldap/lldap#20ade89633164ee069fca6f4b925a79a55c62f93"
dependencies = [ dependencies = [
"chrono", "chrono",
"curve25519-dalek", "curve25519-dalek",
@ -1445,7 +1458,7 @@ dependencies = [
"generic-array", "generic-array",
"getrandom 0.2.15", "getrandom 0.2.15",
"opaque-ke", "opaque-ke",
"rand", "rand 0.8.5",
"rust-argon2", "rust-argon2",
"serde", "serde",
"sha2 0.9.9", "sha2 0.9.9",
@ -1489,7 +1502,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1572,9 +1585,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.3" version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@ -1596,7 +1609,7 @@ dependencies = [
"generic-bytes", "generic-bytes",
"hkdf", "hkdf",
"hmac", "hmac",
"rand", "rand 0.8.5",
"serde", "serde",
"subtle", "subtle",
"thiserror 1.0.69", "thiserror 1.0.69",
@ -1639,7 +1652,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1733,7 +1746,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1749,22 +1762,22 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.9" version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.1.9" version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1781,11 +1794,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.20" version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [ dependencies = [
"zerocopy", "zerocopy 0.8.23",
] ]
[[package]] [[package]]
@ -1811,7 +1824,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"version_check", "version_check",
"yansi", "yansi",
] ]
@ -1827,11 +1840,12 @@ dependencies = [
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.6" version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg_aliases",
"pin-project-lite", "pin-project-lite",
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
@ -1841,17 +1855,18 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
"tracing", "tracing",
"web-time",
] ]
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.9" version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
dependencies = [ dependencies = [
"bytes", "bytes",
"getrandom 0.2.15", "getrandom 0.3.2",
"rand", "rand 0.9.0",
"ring", "ring",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
@ -1874,18 +1889,24 @@ dependencies = [
"once_cell", "once_cell",
"socket2", "socket2",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.39" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -1893,10 +1914,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
"zerocopy 0.8.23",
]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.3.1" version = "0.3.1"
@ -1907,6 +1939,16 @@ dependencies = [
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.3",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.5.1" version = "0.5.1"
@ -1925,6 +1967,15 @@ dependencies = [
"getrandom 0.2.15", "getrandom 0.2.15",
] ]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
dependencies = [
"getrandom 0.3.2",
]
[[package]] [[package]]
name = "random-number" name = "random-number"
version = "0.1.9" version = "0.1.9"
@ -1932,7 +1983,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc8cdd49be664772ffc3dbfa743bb8c34b78f9cc6a9f50e56ae878546796067" checksum = "7fc8cdd49be664772ffc3dbfa743bb8c34b78f9cc6a9f50e56ae878546796067"
dependencies = [ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"rand", "rand 0.8.5",
"random-number-macro-impl", "random-number-macro-impl",
] ]
@ -1944,7 +1995,7 @@ checksum = "f5135143cb48d14289139e4615bffec0d59b4cbfd4ea2398a3770bd2abfc4aa2"
dependencies = [ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -1982,7 +2033,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2031,9 +2082,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.14" version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@ -2111,9 +2162,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.23" version = "0.23.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
dependencies = [ dependencies = [
"log", "log",
"once_cell", "once_cell",
@ -2169,9 +2220,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.102.8" version = "0.103.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f"
dependencies = [ dependencies = [
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@ -2221,7 +2272,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2302,7 +2353,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2313,7 +2364,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2486,9 +2537,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.99" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2512,7 +2563,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2541,7 +2592,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2552,7 +2603,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2592,9 +2643,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.44.0" version = "1.44.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2616,7 +2667,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2631,9 +2682,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.13" version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -2710,7 +2761,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -2863,6 +2914,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.100" version = "0.2.100"
@ -2885,7 +2945,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2920,7 +2980,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -3006,9 +3066,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.1.0" version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
@ -3023,9 +3083,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-result" name = "windows-result"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [ dependencies = [
"windows-link", "windows-link",
] ]
@ -3185,6 +3245,15 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "write16" name = "write16"
version = "1.0.0" version = "1.0.0"
@ -3223,7 +3292,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"synstructure", "synstructure",
] ]
@ -3233,8 +3302,16 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder", "zerocopy-derive 0.7.35",
"zerocopy-derive", ]
[[package]]
name = "zerocopy"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
dependencies = [
"zerocopy-derive 0.8.23",
] ]
[[package]] [[package]]
@ -3245,7 +3322,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
] ]
[[package]] [[package]]
@ -3265,7 +3353,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
"synstructure", "synstructure",
] ]
@ -3286,7 +3374,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]
[[package]] [[package]]
@ -3308,5 +3396,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.99", "syn 2.0.100",
] ]

View File

@ -1,7 +1,7 @@
[package] [package]
name = "lldap-controller" name = "lldap-controller"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
default-run = "lldap-controller" default-run = "lldap-controller"
[workspace] [workspace]
@ -15,7 +15,7 @@ insta = { version = "1.42.2", features = ["yaml"] }
queries = { path = "./queries" } queries = { path = "./queries" }
anyhow = "1.0.97" anyhow = "1.0.97"
lldap_auth = { git = "https://github.com/lldap/lldap" } lldap_auth = { git = "https://github.com/lldap/lldap" }
rand = { version = "0.8.0" } rand = { version = "0.8.5" }
serde_json = "1.0.140" serde_json = "1.0.140"
cynic = { workspace = true, features = ["http-reqwest"] } cynic = { workspace = true, features = ["http-reqwest"] }
tokio = { version = "1.44.0", features = ["full"] } tokio = { version = "1.44.0", features = ["full"] }
@ -30,7 +30,6 @@ tracing = "0.1.41"
thiserror = "2.0.12" thiserror = "2.0.12"
chrono = "0.4.40" chrono = "0.4.40"
passwords = "3.1.16" passwords = "3.1.16"
async-trait = "0.1.88"
reqwest = { version = "0.12.14", default-features = false, features = [ reqwest = { version = "0.12.14", default-features = false, features = [
"json", "json",
"rustls-tls", "rustls-tls",

View File

@ -1,9 +1,21 @@
FROM rust:1.85 AS builder FROM rust:1.85 AS base
WORKDIR /usr/src/lldap-controller ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
ADD . . RUN cargo install cargo-chef --locked --version 0.1.71 && \
RUN cargo install --path . cargo install cargo-auditable --locked --version 0.6.6
WORKDIR /app
FROM debian:bookworm-slim FROM base AS planner
COPY --from=builder /usr/local/cargo/bin/lldap-controller /usr/local/bin/lldap-controller COPY . .
COPY --from=builder /usr/local/cargo/bin/crdgen /usr/local/bin/crdgen RUN cargo chef prepare --recipe-path recipe.json
CMD ["lldap-controller"]
FROM base AS builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
RUN cargo auditable build --release
FROM gcr.io/distroless/cc-debian12:nonroot AS runtime
COPY --from=builder /app/target/release/lldap-controller /lldap-controller
COPY --from=builder /app/target/release/crdgen /crdgen
CMD ["/lldap-controller"]

View File

@ -21,7 +21,7 @@ spec:
securityContext: {} securityContext: {}
containers: containers:
- name: lldap-controller - name: lldap-controller
image: git.huizinga.dev/dreaded_x/lldap-controller:sha-${SHA_SHORT} image: git.huizinga.dev/dreaded_x/lldap-controller@${DIGEST}
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
securityContext: {} securityContext: {}
resources: resources:

View File

@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
namespace: lldap namespace: lldap
resources: resources:
- ./crds.yaml
- ./service-account.yaml - ./service-account.yaml
- ./cluster-role.yaml - ./cluster-role.yaml
- ./cluster-role-binding.yaml - ./cluster-role-binding.yaml

View File

@ -86,11 +86,35 @@ pub struct GetGroups {
pub groups: Vec<Group>, pub groups: Vec<Group>,
} }
#[derive(cynic::QueryVariables, Debug)]
pub struct CreateGroupVariables<'a> {
pub name: &'a str,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "CreateGroupVariables")]
pub struct CreateGroup {
#[arguments(name: $name)]
pub create_group: Group,
}
#[derive(cynic::QueryVariables, Debug)]
pub struct DeleteGroupVariables {
pub id: i32,
}
#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Mutation", variables = "DeleteGroupVariables")]
pub struct DeleteGroup {
#[arguments(groupId: $id)]
pub delete_group: Success,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cynic::{MutationBuilder, QueryBuilder};
use super::*; use super::*;
use cynic::MutationBuilder;
use cynic::QueryBuilder;
#[test] #[test]
fn delete_user_gql_output() { fn delete_user_gql_output() {
@ -139,4 +163,18 @@ mod tests {
insta::assert_snapshot!(operation.query); insta::assert_snapshot!(operation.query);
} }
#[test]
fn create_group_gql_output() {
let operation = CreateGroup::build(CreateGroupVariables { name: "group" });
insta::assert_snapshot!(operation.query);
}
#[test]
fn delete_group_gql_output() {
let operation = DeleteGroup::build(DeleteGroupVariables { id: 0 });
insta::assert_snapshot!(operation.query);
}
} }

View File

@ -0,0 +1,10 @@
---
source: queries/src/lib.rs
expression: operation.query
---
mutation CreateGroup($name: String!) {
createGroup(name: $name) {
id
displayName
}
}

View File

@ -0,0 +1,9 @@
---
source: queries/src/lib.rs
expression: operation.query
---
mutation DeleteGroup($id: Int!) {
deleteGroup(groupId: $id) {
ok
}
}

4
rust-toolchain.toml Normal file
View File

@ -0,0 +1,4 @@
[toolchain]
channel = "1.85"
profile = "default"
components = ["rust-analyzer"]

View File

@ -2,7 +2,8 @@ use kube::CustomResourceExt;
fn main() { fn main() {
print!( print!(
"{}", "{}---\n{}",
serde_yaml::to_string(&lldap_controller::resources::ServiceUser::crd()).unwrap() serde_yaml::to_string(&lldap_controller::resources::ServiceUser::crd()).unwrap(),
serde_yaml::to_string(&lldap_controller::resources::Group::crd()).unwrap()
) )
} }

View File

@ -1,12 +1,10 @@
use async_trait::async_trait;
use k8s_openapi::api::core::v1::Secret; use k8s_openapi::api::core::v1::Secret;
use kube::{ use kube::runtime::events::{Event, EventType, Recorder, Reporter};
runtime::events::{Event, EventType, Recorder, Reporter}, use kube::{Resource, ResourceExt};
Resource, ResourceExt,
};
use crate::lldap::LldapConfig; use crate::lldap::LldapConfig;
#[derive(Clone)]
pub struct Context { pub struct Context {
pub client: kube::Client, pub client: kube::Client,
pub lldap_config: LldapConfig, pub lldap_config: LldapConfig,
@ -28,7 +26,7 @@ impl Context {
} }
} }
#[async_trait] #[allow(async_fn_in_trait)]
pub trait ControllerEvents { pub trait ControllerEvents {
type Error; type Error;
@ -40,16 +38,23 @@ pub trait ControllerEvents {
where where
T: Resource<DynamicType = ()> + Sync; T: Resource<DynamicType = ()> + Sync;
async fn group_created<T>(&self, obj: &T, name: &str) -> Result<(), Self::Error>
where
T: Resource<DynamicType = ()> + Sync;
async fn user_deleted<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error> async fn user_deleted<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error>
where where
T: Resource<DynamicType = ()> + Sync; T: Resource<DynamicType = ()> + Sync;
async fn group_deleted<T>(&self, obj: &T, name: &str) -> Result<(), Self::Error>
where
T: Resource<DynamicType = ()> + Sync;
async fn user_not_found<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error> async fn user_not_found<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error>
where where
T: Resource<DynamicType = ()> + Sync; T: Resource<DynamicType = ()> + Sync;
} }
#[async_trait]
impl ControllerEvents for Recorder { impl ControllerEvents for Recorder {
type Error = kube::Error; type Error = kube::Error;
@ -87,6 +92,23 @@ impl ControllerEvents for Recorder {
.await .await
} }
async fn group_created<T>(&self, obj: &T, name: &str) -> Result<(), Self::Error>
where
T: Resource<DynamicType = ()> + Sync,
{
self.publish(
&Event {
type_: EventType::Normal,
reason: "GroupCreated".into(),
note: Some(format!("Created group '{name}'")),
action: "GroupCreated".into(),
secondary: None,
},
&obj.object_ref(&()),
)
.await
}
async fn user_deleted<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error> async fn user_deleted<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error>
where where
T: Resource<DynamicType = ()> + Sync, T: Resource<DynamicType = ()> + Sync,
@ -104,6 +126,23 @@ impl ControllerEvents for Recorder {
.await .await
} }
async fn group_deleted<T>(&self, obj: &T, name: &str) -> Result<(), Self::Error>
where
T: Resource<DynamicType = ()> + Sync,
{
self.publish(
&Event {
type_: EventType::Normal,
reason: "GroupDeleted".into(),
note: Some(format!("Deleted group '{name}'")),
action: "GroupDeleted".into(),
secondary: None,
},
&obj.object_ref(&()),
)
.await
}
async fn user_not_found<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error> async fn user_not_found<T>(&self, obj: &T, username: &str) -> Result<(), Self::Error>
where where
T: Resource<DynamicType = ()> + Sync, T: Resource<DynamicType = ()> + Sync,

View File

@ -1,19 +1,20 @@
use anyhow::Context;
use lldap_auth::opaque::AuthenticationError;
use lldap_auth::registration::ServerRegistrationStartResponse;
use lldap_auth::{opaque, registration};
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use std::time::Duration; use std::time::Duration;
use tracing::{debug, trace};
use anyhow::Context;
use cynic::http::{CynicReqwestError, ReqwestExt}; use cynic::http::{CynicReqwestError, ReqwestExt};
use cynic::{GraphQlError, GraphQlResponse, MutationBuilder, QueryBuilder}; use cynic::{GraphQlError, GraphQlResponse, MutationBuilder, QueryBuilder};
use lldap_auth::login::{ClientSimpleLoginRequest, ServerLoginResponse}; use lldap_auth::login::{ClientSimpleLoginRequest, ServerLoginResponse};
use lldap_auth::opaque::AuthenticationError;
use lldap_auth::registration::ServerRegistrationStartResponse;
use lldap_auth::{opaque, registration};
use queries::{ use queries::{
AddUserToGroup, AddUserToGroupVariables, CreateUser, CreateUserVariables, DeleteUser, AddUserToGroup, AddUserToGroupVariables, CreateGroup, CreateGroupVariables, CreateUser,
DeleteUserVariables, GetGroups, GetUser, GetUserVariables, Group, RemoveUserFromGroup, CreateUserVariables, DeleteGroup, DeleteGroupVariables, DeleteUser, DeleteUserVariables,
RemoveUserFromGroupVariables, User, GetGroups, GetUser, GetUserVariables, Group, RemoveUserFromGroup, RemoveUserFromGroupVariables,
User,
}; };
use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue};
use tracing::{debug, trace};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
@ -41,6 +42,7 @@ fn check_graphql_errors<T>(response: GraphQlResponse<T>) -> Result<T> {
.expect("Data should be valid if there are no error")) .expect("Data should be valid if there are no error"))
} }
#[derive(Clone)]
pub struct LldapConfig { pub struct LldapConfig {
username: String, username: String,
password: String, password: String,
@ -150,6 +152,32 @@ impl LldapClient {
Ok(check_graphql_errors(response)?.groups) Ok(check_graphql_errors(response)?.groups)
} }
pub async fn create_group(&self, name: &str) -> Result<Group> {
let operation = CreateGroup::build(CreateGroupVariables { name });
let response = self
.client
.post(format!("{}/api/graphql", self.url))
.run_graphql(operation)
.await?;
Ok(check_graphql_errors(response)?.create_group)
}
pub async fn delete_group(&self, id: i32) -> Result<()> {
let operation = DeleteGroup::build(DeleteGroupVariables { id });
let response = self
.client
.post(format!("{}/api/graphql", self.url))
.run_graphql(operation)
.await?;
check_graphql_errors(response)?;
Ok(())
}
pub async fn add_user_to_group(&self, username: &str, group: i32) -> Result<()> { pub async fn add_user_to_group(&self, username: &str, group: i32) -> Result<()> {
let operation = AddUserToGroup::build(AddUserToGroupVariables { username, group }); let operation = AddUserToGroup::build(AddUserToGroupVariables { username, group });

View File

@ -1,32 +1,49 @@
use std::{sync::Arc, time::Duration}; use std::sync::Arc;
use std::time::Duration;
use futures::StreamExt; use futures::StreamExt;
use k8s_openapi::api::core::v1::Secret; use k8s_openapi::api::core::v1::Secret;
use kube::{ use kube::runtime::controller::{self, Action};
runtime::{controller::Action, Controller}, use kube::runtime::reflector::ObjectRef;
Api, Client as KubeClient, use kube::runtime::{Controller, watcher};
}; use kube::{Api, Client as KubeClient, Resource};
use lldap_controller::{ use lldap_controller::context::Context;
context::Context, use lldap_controller::lldap::LldapConfig;
lldap::LldapConfig, use lldap_controller::resources::{self, Error, Group, ServiceUser, reconcile};
resources::{self, reconcile, ServiceUser},
};
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry}; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{EnvFilter, Registry};
fn error_policy(_obj: Arc<ServiceUser>, err: &resources::Error, _ctx: Arc<Context>) -> Action { fn error_policy<T>(_obj: Arc<T>, err: &resources::Error, _ctx: Arc<Context>) -> Action {
warn!("error: {}", err); warn!("error: {}", err);
Action::requeue(Duration::from_secs(5)) Action::requeue(Duration::from_secs(5))
} }
async fn log_status<T>(
res: Result<(ObjectRef<T>, Action), controller::Error<Error, watcher::Error>>,
) where
T: Resource,
{
match res {
Ok(obj) => debug!("reconciled {:?}", obj.0.name),
Err(err) => warn!("reconcile failed: {}", err),
}
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
let logger = tracing_subscriber::fmt::layer().json();
let env_filter = EnvFilter::try_from_default_env() let env_filter = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info")) .or_else(|_| EnvFilter::try_new("info"))
.expect("Fallback should be valid"); .expect("Fallback should be valid");
Registry::default().with(logger).with(env_filter).init(); if std::env::var("CARGO").is_ok() {
let logger = tracing_subscriber::fmt::layer().compact();
Registry::default().with(logger).with(env_filter).init();
} else {
let logger = tracing_subscriber::fmt::layer().json();
Registry::default().with(logger).with(env_filter).init();
}
info!("Starting controller"); info!("Starting controller");
@ -41,17 +58,20 @@ async fn main() -> anyhow::Result<()> {
let service_users = Api::<ServiceUser>::all(client.clone()); let service_users = Api::<ServiceUser>::all(client.clone());
let secrets = Api::<Secret>::all(client.clone()); let secrets = Api::<Secret>::all(client.clone());
Controller::new(service_users.clone(), Default::default()) let service_user_controller = Controller::new(service_users, Default::default())
.owns(secrets, Default::default()) .owns(secrets, Default::default())
.shutdown_on_signal()
.run(reconcile, error_policy, Arc::new(data.clone()))
.for_each(log_status);
let groups = Api::<Group>::all(client.clone());
let group_controller = Controller::new(groups, Default::default())
.shutdown_on_signal() .shutdown_on_signal()
.run(reconcile, error_policy, Arc::new(data)) .run(reconcile, error_policy, Arc::new(data))
.for_each(|res| async move { .for_each(log_status);
match res {
Ok(obj) => debug!("reconciled {:?}", obj.0.name), tokio::join!(service_user_controller, group_controller);
Err(err) => warn!("reconcile failed: {}", err),
}
})
.await;
Ok(()) Ok(())
} }

76
src/resources/group.rs Normal file
View File

@ -0,0 +1,76 @@
use std::sync::Arc;
use std::time::Duration;
use kube::CustomResource;
use kube::runtime::controller::Action;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tracing::{debug, trace};
use super::{Error, Reconcile, Result};
use crate::context::{Context, ControllerEvents};
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[kube(kind = "Group", group = "lldap.huizinga.dev", version = "v1")]
#[kube(
shortname = "lg",
doc = "Custom resource for managing Groups inside of LLDAP"
)]
#[serde(rename_all = "camelCase")]
pub struct GroupSpec {}
impl Reconcile for Group {
async fn reconcile(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action> {
let name = self
.metadata
.name
.clone()
.ok_or(Error::MissingObjectKey(".metadata.name"))?;
debug!(name, "Apply");
let lldap_client = ctx.lldap_config.build_client().await?;
trace!(name, "Get existing groups");
let groups = lldap_client.get_groups().await?;
if !groups.iter().any(|group| group.display_name == name) {
trace!("Group does not exist yet");
lldap_client.create_group(&name).await?;
ctx.recorder.group_created(self.as_ref(), &name).await?;
} else {
trace!("Group already exists");
}
Ok(Action::requeue(Duration::from_secs(3600)))
}
async fn cleanup(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action> {
let name = self
.metadata
.name
.clone()
.ok_or(Error::MissingObjectKey(".metadata.name"))?;
debug!(name, "Cleanup");
let lldap_client = ctx.lldap_config.build_client().await?;
trace!(name, "Get existing groups");
let groups = lldap_client.get_groups().await?;
if let Some(group) = groups.iter().find(|group| group.display_name == name) {
trace!(name, "Deleting group");
lldap_client.delete_group(group.id).await?;
ctx.recorder.group_deleted(self.as_ref(), &name).await?;
} else {
trace!(name, "Group does not exist")
}
Ok(Action::await_change())
}
}

66
src/resources/mod.rs Normal file
View File

@ -0,0 +1,66 @@
mod group;
mod service_user;
use core::fmt;
use std::sync::Arc;
use kube::runtime::controller::Action;
use kube::runtime::finalizer;
use kube::{Api, Resource, ResourceExt};
use serde::Serialize;
use serde::de::DeserializeOwned;
use tracing::{debug, instrument};
pub use self::group::Group;
pub use self::service_user::ServiceUser;
use crate::context::Context;
use crate::lldap;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Failed to commit: {0}")]
Commit(#[from] kube::api::entry::CommitError),
#[error("Kube api error: {0}")]
Kube(#[from] kube::Error),
#[error("LLDAP error: {0}")]
Lldap(#[from] lldap::Error),
#[error("Finalizer error: {0}")]
Finalizer(#[source] Box<finalizer::Error<Self>>),
#[error("MissingObjectKey: {0}")]
MissingObjectKey(&'static str),
}
impl From<finalizer::Error<Self>> for Error {
fn from(error: finalizer::Error<Self>) -> Self {
Self::Finalizer(Box::new(error))
}
}
type Result<T, E = Error> = std::result::Result<T, E>;
trait Reconcile {
async fn reconcile(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action>;
async fn cleanup(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action>;
}
#[instrument(skip(obj, ctx))]
pub async fn reconcile<T>(obj: Arc<T>, ctx: Arc<Context>) -> Result<Action>
where
T: Resource + ResourceExt + Clone + Serialize + DeserializeOwned + fmt::Debug + Reconcile,
<T as Resource>::DynamicType: Default,
{
debug!(name = obj.name_any(), "Reconcile");
let service_users = Api::<T>::all(ctx.client.clone());
Ok(
finalizer(&service_users, &ctx.controller_name, obj, |event| async {
match event {
finalizer::Event::Apply(obj) => obj.reconcile(ctx.clone()).await,
finalizer::Event::Cleanup(obj) => obj.cleanup(ctx.clone()).await,
}
})
.await?,
)
}

View File

@ -1,50 +1,24 @@
use core::fmt;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::str::from_utf8; use std::str::from_utf8;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use k8s_openapi::api::core::v1::Secret; use k8s_openapi::api::core::v1::Secret;
use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference;
use k8s_openapi::NamespaceResourceScope;
use kube::api::{ObjectMeta, Patch, PatchParams, PostParams}; use kube::api::{ObjectMeta, Patch, PatchParams, PostParams};
use kube::runtime::controller::Action; use kube::runtime::controller::Action;
use kube::runtime::finalizer; use kube::{Api, CustomResource, Resource};
use kube::{Api, CustomResource, Resource, ResourceExt};
use passwords::PasswordGenerator; use passwords::PasswordGenerator;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use tracing::{debug, instrument, trace, warn}; use tracing::{debug, trace, warn};
use super::{Error, Reconcile, Result};
use crate::context::{Context, ControllerEvents}; use crate::context::{Context, ControllerEvents};
use crate::lldap; use crate::lldap;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Failed to commit: {0}")]
Commit(#[from] kube::api::entry::CommitError),
#[error("Kube api error: {0}")]
Kube(#[from] kube::Error),
#[error("LLDAP error: {0}")]
Lldap(#[from] lldap::Error),
#[error("Finalizer error: {0}")]
Finalizer(#[source] Box<finalizer::Error<Self>>),
#[error("MissingObjectKey: {0}")]
MissingObjectKey(&'static str),
}
impl From<finalizer::Error<Self>> for Error {
fn from(error: finalizer::Error<Self>) -> Self {
Self::Finalizer(Box::new(error))
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)] #[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
#[kube( #[kube(
kind = "ServiceUser", kind = "ServiceUser",
@ -97,46 +71,10 @@ fn new_secret(username: &str, oref: OwnerReference) -> Secret {
} }
} }
#[async_trait]
trait Reconcile {
async fn reconcile(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action>;
async fn cleanup(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action>;
}
#[instrument(skip(obj, ctx))]
pub async fn reconcile<T>(obj: Arc<T>, ctx: Arc<Context>) -> Result<Action>
where
T: Resource<Scope = NamespaceResourceScope>
+ ResourceExt
+ Clone
+ Serialize
+ DeserializeOwned
+ fmt::Debug
+ Reconcile,
<T as Resource>::DynamicType: Default,
{
debug!(name = obj.name_any(), "Reconcile");
let namespace = obj.namespace().expect("Resource is namespace scoped");
let service_users = Api::<T>::namespaced(ctx.client.clone(), &namespace);
Ok(
finalizer(&service_users, &ctx.controller_name, obj, |event| async {
match event {
finalizer::Event::Apply(obj) => obj.reconcile(ctx.clone()).await,
finalizer::Event::Cleanup(obj) => obj.cleanup(ctx.clone()).await,
}
})
.await?,
)
}
fn format_username(name: &str, namespace: &str) -> String { fn format_username(name: &str, namespace: &str) -> String {
format!("{name}.{namespace}") format!("{name}.{namespace}")
} }
#[async_trait]
impl Reconcile for ServiceUser { impl Reconcile for ServiceUser {
async fn reconcile(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action> { async fn reconcile(self: Arc<Self>, ctx: Arc<Context>) -> Result<Action> {
let name = self let name = self
@ -289,9 +227,10 @@ impl Reconcile for ServiceUser {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use kube::CustomResourceExt; use kube::CustomResourceExt;
use super::*;
#[test] #[test]
fn service_user_crd_output() { fn service_user_crd_output() {
insta::assert_yaml_snapshot!(ServiceUser::crd()); insta::assert_yaml_snapshot!(ServiceUser::crd());

5
yaml/group.yaml Normal file
View File

@ -0,0 +1,5 @@
apiVersion: lldap.huizinga.dev/v1
kind: Group
metadata:
name: test-group
spec: {}

View File

@ -1,6 +1,5 @@
apiVersion: lldap.huizinga.dev/v1 apiVersion: lldap.huizinga.dev/v1
kind: ServiceUser kind: ServiceUser
metadata: metadata:
name: authelia name: test-user
spec: spec: {}
passwordManager: false