From 0380e02f3f4220278470a1c62eccfb6fab3c8715 Mon Sep 17 00:00:00 2001 From: Kai Kriegel Date: Mon, 2 Jan 2023 03:59:09 +0000 Subject: [PATCH 1/2] reworked CI --- .gitlab-ci.yml | 229 +++++++++++++++++--- Dockerfile | 29 +-- bffhd/resources/state/value.rs | 4 - cargo-cross-config | 8 + runtime/lightproc/examples/proc_panic.rs | 3 +- runtime/lightproc/examples/proc_run.rs | 3 +- runtime/lightproc/src/lightproc.rs | 17 +- runtime/lightproc/src/recoverable_handle.rs | 14 +- 8 files changed, 241 insertions(+), 66 deletions(-) create mode 100644 cargo-cross-config diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 412fb9b..ff1d3d3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,21 +2,28 @@ # Additionally, lint the code before anything else to fail more quickly stages: - lint + - check - build - test - release - dockerify default: - image: "rust:latest" + image: "registry.gitlab.com/fabinfra/rust-builder:latest" tags: - linux - docker + - fabinfra variables: GIT_SUBMODULE_STRATEGY: recursive CARGO_HOME: $CI_PROJECT_DIR/cargo APT_CACHE_DIR: $CI_PROJECT_DIR/apt + FF_USE_FASTZIP: "true" # enable fastzip - a faster zip implementation that also supports level configuration. + ARTIFACT_COMPRESSION_LEVEL: fast # can also be set to fastest, fast, slow and slowest. If just enabling fastzip is not enough try setting this to fastest or fast. + CACHE_COMPRESSION_LEVEL: fastest # same as above, but for caches + TRANSFER_METER_FREQUENCY: 5s # will display transfer progress every 5 seconds for artifacts and remote caches. + # cache dependencies and build environment to speed up setup cache: @@ -26,10 +33,6 @@ cache: - cargo/ - target/ -# install build dependencies -before_script: - - apt-get update -yqq - - apt-get install -o dir::cache::archives="$APT_CACHE_DIR" -yqq --no-install-recommends capnproto build-essential cmake clang libclang-dev jq .lints: stage: lint @@ -41,7 +44,6 @@ before_script: lint:clippy: extends: .lints script: - - rustup component add clippy - cargo clippy -V - echo -e "\e[0Ksection_start:`date +%s`:clippy_output\r\e[0Kcargo clippy output" - cargo clippy -- --no-deps @@ -51,15 +53,14 @@ lint:clippy: lint:fmt: extends: .lints script: - - rustup component add rustfmt - cargo fmt --version - echo -e "\e[0Ksection_start:`date +%s`:rustfmt_output\r\e[0KChanges suggested by rustfmt" - cargo fmt --check -- -v - echo -e "\e[0Ksection_end:`date +%s`:rustfmt_output\r\e[0K" # Check if the code builds on rust stable -stable:build: - stage: build +stable:check: + stage: check only: - main - development @@ -70,9 +71,94 @@ stable:build: - cargo check --verbose - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" +# Check if the code builds on rust stable on armv7 +stable:check:armhf: + stage: check + only: + - main + - development + - merge_requests + before_script: + - mkdir -p $CARGO_HOME + - cp cargo-cross-config $CARGO_HOME/config.toml + script: + - rustc +stable --version && cargo --version + - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo check with target armv7-unknown-linux-gnueabihf" + - cargo check --verbose --target armv7-unknown-linux-gnueabihf + - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" + + # Check if the code builds on rust stable on arm64 +stable:check:arm64: + stage: check + only: + - main + - development + - merge_requests + before_script: + - mkdir -p $CARGO_HOME + - cp cargo-cross-config $CARGO_HOME/config.toml + script: + - rustc +stable --version && cargo --version + - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo check with target aarch64-unknown-linux-gnu" + - cargo check --verbose --target aarch64-unknown-linux-gnu + - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" + +# Check if the code builds on rust stable +stable:build:amd64: + stage: build + only: + - main + - development + - merge_requests + script: + - rustc +stable --version && cargo --version + - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo build with target x86_64-unknown-linux-gnu" + - cargo build --release --target x86_64-unknown-linux-gnu + - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" + artifacts: + paths: + - target/x86_64-unknown-linux-gnu/release/bffhd + + +# Check if the code builds on rust stable on armv7 +stable:build:armhf: + stage: build + only: + - main + - development + before_script: + - mkdir -p $CARGO_HOME + - cp cargo-cross-config $CARGO_HOME/config.toml + script: + - rustc +stable --version && cargo --version + - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo build with target armv7-unknown-linux-gnueabihf" + - cargo build --release --target armv7-unknown-linux-gnueabihf + - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" + artifacts: + paths: + - target/armv7-unknown-linux-gnueabihf/release/bffhd + + # Check if the code builds on rust stable on arm64 +stable:build:arm64: + stage: build + only: + - main + - development + before_script: + - mkdir -p $CARGO_HOME + - cp cargo-cross-config $CARGO_HOME/config.toml + script: + - rustc +stable --version && cargo --version + - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo build with target aarch64-unknown-linux-gnu" + - cargo build --release --target aarch64-unknown-linux-gnu + - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" + artifacts: + paths: + - target/aarch64-unknown-linux-gnu/release/bffhd + stable:test: stage: build - needs: ["stable:build"] + needs: ["stable:check"] only: - main - development @@ -80,14 +166,12 @@ stable:test: script: - echo -e "\e[0Ksection_start:`date +%s`:build_output\r\e[0KOutput of cargo test --no-run" - cargo test --verbose --no-run --workspace - - echo -e "\e[0Ksection_end:`date +%s`:build_output\r\e[0K" - - cargo install --root $CARGO_HOME cargo2junit .tests: stage: test needs: ["stable:test"] script: - - cargo test --workspace $TEST_TARGET -- -Z unstable-options --format json --report-time | $CARGO_HOME/bin/cargo2junit > report.xml + - cargo test --workspace $TEST_TARGET -- -Z unstable-options --format json --report-time | cargo2junit > report.xml artifacts: when: always reports: @@ -114,6 +198,23 @@ unit test 3:3: TEST_TARGET: "--examples" extends: .tests +upload_binaries: + stage: release + image: curlimages/curl:latest + before_script: [] + cache: [] + dependencies: + - stable:build:amd64 + - stable:build:armhf + - stable:build:arm64 + script: + - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file target/aarch64-unknown-linux-gnu/release/bffhd "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${CI_COMMIT_TAG}/bffhd_${VERSION}_linux_arm64"' + - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file target/x86_64-unknown-linux-gnu/release/bffhd "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${CI_COMMIT_TAG}/bffhd_${VERSION}_linux_amd64"' + - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file target/armv7-unknown-linux-gnueabihf/release/bffhd "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${VERSION}/bffhd_${VERSION}_linux_arm"' + rules: + - if: $CI_COMMIT_TAG =~ "release/.*" + when: never + - if: $CI_COMMIT_BRANCH == "main" release_prepare: stage: release @@ -144,32 +245,106 @@ release_job: name: "BFFH $VERSION" description: "GitLab CI auto-created release" tag_name: "release/$VERSION" + assets: + links: + - name: 'bffhd AMD64' + url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${VERSION}/bffhd_${VERSION}_linux_amd64" + - name: 'bffhd ARMv7' + url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${VERSION}/bffhd_${VERSION}_linux_arm" + - name: 'bffhd ARM64' + url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/bffhd/${VERSION}/bffhd_${VERSION}_linux_arm64" build:docker-releases: stage: dockerify - image: - name: gcr.io/kaniko-project/executor:v1.6.0-debug - entrypoint: [""] + image: jdrouet/docker-with-buildx:latest + dependencies: + - stable:build:amd64 + - stable:build:armhf + - stable:build:arm64 + tags: + - linux + - docker + - fabinfra + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + TRIVY_NO_PROGRESS: "true" + TRIVY_CACHE_DIR: ".trivycache/" + services: + - docker:dind before_script: - - '' + - export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') + - echo $TRIVY_VERSION + - wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf - script: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --force --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + - docker login $CI_REGISTRY -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - docker buildx create --name cibuilder --driver docker-container --use + - docker buildx ls + - docker buildx inspect --bootstrap + - docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG . + - docker buildx build --load --platform linux/amd64 -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG . + # Build report + - ./trivy image --exit-code 0 --format template --template "@contrib/gitlab.tpl" -o gl-container-scanning-report.json $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + # Print report + - ./trivy image --exit-code 0 --severity HIGH $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + # Fail on severe vulnerabilities + - ./trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + cache: + paths: + - .trivycache/ + artifacts: + reports: + container_scanning: gl-container-scanning-report.json rules: - if: $CI_COMMIT_TAG =~ "release/.*" when: never build:docker-development: stage: dockerify - image: - name: gcr.io/kaniko-project/executor:v1.6.0-debug - entrypoint: [""] + image: jdrouet/docker-with-buildx:latest + dependencies: + - stable:build:amd64 + - stable:build:armhf + - stable:build:arm64 + tags: + - linux + - docker + - fabinfra + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + DOCKER_TLS_CERTDIR: "" + TRIVY_NO_PROGRESS: "true" + TRIVY_CACHE_DIR: ".trivycache/" + services: + - docker:dind before_script: - - '' + - export TRIVY_VERSION=$(wget -qO - "https://api.github.com/repos/aquasecurity/trivy/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') + - echo $TRIVY_VERSION + - wget --no-verbose https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz -O - | tar -zxvf - script: - - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --force --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:dev-latest + - docker login $CI_REGISTRY -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - docker buildx create --name cibuilder --driver docker-container --use + - docker buildx ls + - docker buildx inspect --bootstrap + - docker buildx build --platform linux/arm/v7,linux/arm64,linux/amd64 -t $CI_REGISTRY_IMAGE:development . + - docker buildx build --load --platform linux/amd64 -t $CI_REGISTRY_IMAGE:development . + # Build report + - ./trivy image --exit-code 0 --format template --template "@contrib/gitlab.tpl" -o gl-container-scanning-report.json $CI_REGISTRY_IMAGE:development + # Print report + - ./trivy image --exit-code 0 --severity HIGH $CI_REGISTRY_IMAGE:development + # Fail on severe vulnerabilities + - ./trivy image --exit-code 1 --severity CRITICAL $CI_REGISTRY_IMAGE:development + - docker push $CI_REGISTRY_IMAGE:development + cache: + paths: + - .trivycache/ + artifacts: + reports: + container_scanning: gl-container-scanning-report.json only: - development diff --git a/Dockerfile b/Dockerfile index 0732c9c..418c22e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,23 @@ -# Setup build image for multistage build -FROM rust:bullseye as builder -# install build deps -RUN apt-get update && apt-get upgrade -y -RUN apt-get install -yqq --no-install-recommends capnproto python3 python3-pip -RUN pip3 install paho-mqtt +FROM --platform=$BUILDPLATFORM alpine:latest as copy +ARG TARGETPLATFORM +RUN case "$TARGETPLATFORM" in \ + "linux/arm/v7") echo armv7-unknown-linux-gnueabihf > /rust_target.txt ;; \ + "linux/arm/v6") echo arm-unknown-linux-gnueabihf > /rust_target.txt ;; \ + "linux/arm64") echo aarch64-unknown-linux-gnu > /rust_target.txt ;; \ + "linux/amd64") echo x86_64-unknown-linux-gnu > /rust_target.txt ;; \ + *) exit 1 ;; \ +esac WORKDIR /usr/src/bffh COPY . . -RUN cargo build --release - +RUN cp target/$(cat /rust_target.txt)/release/bffhd ./bffhd.bin # Setup deployable image -FROM debian:bullseye-slim -# Install runtime deps -#RUN apt-get update && apt-get upgrade -yqq -COPY --from=builder /usr/src/bffh/target/release/bffhd /usr/local/bin/bffhd -#COPY --from=builder /usr/src/bffh/examples/bffh.dhall /etc/diflouroborane.dhall -# RUN diflouroborane --print-default > /etc/diflouroborane.toml +FROM ubuntu:22.04 +RUN apt-get update && apt-get upgrade -y +RUN apt-get install -yqq --no-install-recommends python3 python3-pip +RUN pip3 install paho-mqtt +COPY --from=copy /usr/src/bffh/bffhd.bin /usr/local/bin/bffhd VOLUME /etc/bffh/ VOLUME /var/lib/bffh/ VOLUME /usr/local/lib/bffh/adapters/ diff --git a/bffhd/resources/state/value.rs b/bffhd/resources/state/value.rs index e87fdde..9b62e2c 100644 --- a/bffhd/resources/state/value.rs +++ b/bffhd/resources/state/value.rs @@ -275,10 +275,6 @@ pub struct ImplDebugInfo { /// [statevalue_register](macro@crate::statevalue_register) macro with your OID as first and type /// as second parameter like so: /// -/// ```no_run -/// struct MyStruct; -/// statevalue_register!(ObjectIdentifier::from_str("1.3.6.1.4.1.48398.612.1.14").unwrap(), MyStruct) -/// ``` pub struct ImplEntry<'a> { id: ImplId<'a>, data: ImplData<'a>, diff --git a/cargo-cross-config b/cargo-cross-config new file mode 100644 index 0000000..ffbdcaa --- /dev/null +++ b/cargo-cross-config @@ -0,0 +1,8 @@ +[target.armv7-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabihf-gcc" + +[target.arm-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabi-gcc" + +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" diff --git a/runtime/lightproc/examples/proc_panic.rs b/runtime/lightproc/examples/proc_panic.rs index 4fc0c20..3db742e 100644 --- a/runtime/lightproc/examples/proc_panic.rs +++ b/runtime/lightproc/examples/proc_panic.rs @@ -30,7 +30,8 @@ where } let schedule = |t| (QUEUE.deref()).send(t).unwrap(); - let (proc, handle) = LightProc::recoverable(future, schedule); + let span = tracing::trace_span!("runtime.spawn", kind = "local"); + let (proc, handle) = LightProc::recoverable(future, schedule, span, None); let handle = handle.on_panic( |err: Box| match err.downcast::<&'static str>() { diff --git a/runtime/lightproc/examples/proc_run.rs b/runtime/lightproc/examples/proc_run.rs index 483a183..bdeb28f 100644 --- a/runtime/lightproc/examples/proc_run.rs +++ b/runtime/lightproc/examples/proc_run.rs @@ -17,7 +17,8 @@ where let future = async move { fut.await }; let schedule = move |t| sender.send(t).unwrap(); - let (proc, handle) = LightProc::build(future, schedule); + let span = tracing::trace_span!("runtime.spawn", kind = "local"); + let (proc, handle) = LightProc::build(future, schedule, span, None); proc.schedule(); diff --git a/runtime/lightproc/src/lightproc.rs b/runtime/lightproc/src/lightproc.rs index 63af3cd..cc0cf25 100644 --- a/runtime/lightproc/src/lightproc.rs +++ b/runtime/lightproc/src/lightproc.rs @@ -9,6 +9,7 @@ //! # Example Usage //! //! ```rust +//! use tracing::Span; //! use lightproc::prelude::*; //! //! // ... future that does work @@ -23,6 +24,8 @@ //! let panic_recoverable = LightProc::recoverable( //! future, //! schedule_function, +//! Span::current(), +//! None, //! ); //! ``` @@ -60,6 +63,7 @@ impl LightProc { /// # Example /// ```rust /// # use std::any::Any; + /// # use tracing::Span; /// # use lightproc::prelude::*; /// # /// # // ... basic schedule function with no waker logic @@ -72,9 +76,11 @@ impl LightProc { /// let (proc, handle) = LightProc::recoverable( /// future, /// schedule_function, + /// Span::current(), + /// None /// ); - /// let handle = handle.on_panic(|s: &mut EmptyProcState, e: Box| { - /// let reason = e.downcast::(); + /// let handle = handle.on_panic(|e: Box| { + /// let reason = e.downcast::().unwrap(); /// println!("future panicked!: {}", &reason); /// }); /// ``` @@ -110,13 +116,6 @@ impl LightProc { /// # // ... basic schedule function with no waker logic /// # fn schedule_function(proc: LightProc) {;} /// # - /// # // ... process stack with a lifecycle callback - /// # let proc_stack = - /// # ProcStack::default() - /// # .with_after_panic(|s: &mut EmptyProcState| { - /// # println!("After panic started!"); - /// # }); - /// # /// // ... creating a standard process /// let standard = LightProc::build( /// future, diff --git a/runtime/lightproc/src/recoverable_handle.rs b/runtime/lightproc/src/recoverable_handle.rs index eb5bc10..d1be89e 100644 --- a/runtime/lightproc/src/recoverable_handle.rs +++ b/runtime/lightproc/src/recoverable_handle.rs @@ -49,8 +49,7 @@ impl RecoverableHandle { /// /// ```rust /// # use std::any::Any; - /// use lightproc::proc_stack::ProcStack; - /// use lightproc::proc_state::EmptyProcState; + /// # use tracing::Span; /// # use lightproc::prelude::*; /// # /// # // ... future that does work @@ -61,21 +60,16 @@ impl RecoverableHandle { /// # // ... basic schedule function with no waker logic /// # fn schedule_function(proc: LightProc) {;} /// # - /// # // ... process stack with a lifecycle callback - /// # let proc_stack = - /// # ProcStack::default() - /// # .with_after_panic(|s: &mut EmptyProcState| { - /// # println!("After panic started!"); - /// # }); - /// # /// // ... creating a recoverable process /// let (proc, recoverable) = LightProc::recoverable( /// future, /// schedule_function, + /// Span::current(), + /// None /// ); /// /// recoverable - /// .on_return(|_e: Box| { + /// .on_panic(|_e: Box| { /// println!("Inner future panicked"); /// }); /// ``` From 0716a75ee618e18b4106e099e5d81da922f38e03 Mon Sep 17 00:00:00 2001 From: Kai Kriegel Date: Mon, 2 Jan 2023 05:00:29 +0000 Subject: [PATCH 2/2] Add support for binary FabReader Mechanism --- bffhd/authentication/fabfire/mod.rs | 4 +- bffhd/authentication/fabfire/server.rs | 48 +- bffhd/authentication/fabfire_bin/mod.rs | 11 + bffhd/authentication/fabfire_bin/server.rs | 526 +++++++++++++++++++++ bffhd/authentication/mod.rs | 55 ++- bffhd/capnp/authenticationsystem.rs | 6 +- bffhd/initiators/process.rs | 17 +- bffhd/session/mod.rs | 6 +- 8 files changed, 636 insertions(+), 37 deletions(-) create mode 100644 bffhd/authentication/fabfire_bin/mod.rs create mode 100644 bffhd/authentication/fabfire_bin/server.rs diff --git a/bffhd/authentication/fabfire/mod.rs b/bffhd/authentication/fabfire/mod.rs index 5d2d5bb..6c7cb62 100644 --- a/bffhd/authentication/fabfire/mod.rs +++ b/bffhd/authentication/fabfire/mod.rs @@ -2,7 +2,7 @@ mod server; pub use server::FabFire; use rsasl::mechname::Mechname; -use rsasl::registry::{Mechanism, MECHANISMS, Side}; +use rsasl::registry::{Mechanism, Side, MECHANISMS}; const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE"); @@ -10,8 +10,8 @@ const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE") pub static FABFIRE: Mechanism = Mechanism::build(MECHNAME, 300, None, Some(FabFire::new_server), Side::Client); -use std::marker::PhantomData; use rsasl::property::SizedProperty; +use std::marker::PhantomData; // All Property types must implement Debug. #[derive(Debug)] diff --git a/bffhd/authentication/fabfire/server.rs b/bffhd/authentication/fabfire/server.rs index 36c61f8..ea1a8a5 100644 --- a/bffhd/authentication/fabfire/server.rs +++ b/bffhd/authentication/fabfire/server.rs @@ -3,7 +3,10 @@ use desfire::desfire::Desfire; use desfire::error::Error as DesfireError; use desfire::iso7816_4::apduresponse::APDUResponse; use rsasl::callback::SessionData; -use rsasl::mechanism::{Authentication, MechanismData, MechanismError, MechanismErrorKind, State, ThisProvider}; +use rsasl::mechanism::{ + Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind, + Provider, State, ThisProvider, +}; use rsasl::prelude::{MessageSent, SASLConfig, SASLError, SessionError}; use rsasl::property::AuthId; use serde::{Deserialize, Serialize}; @@ -62,9 +65,7 @@ impl Display for FabFireError { } } -impl std::error::Error for FabFireError { - -} +impl std::error::Error for FabFireError {} impl MechanismError for FabFireError { fn kind(&self) -> MechanismErrorKind { @@ -92,6 +93,7 @@ struct CardInfo { } struct KeyInfo { + authid: String, key_id: u8, key: Box<[u8]>, } @@ -493,11 +495,20 @@ impl Authentication for FabFire { Ok(_) => { match apdu_response.body { Some(data) => { - let token = String::from_utf8(data).unwrap(); - let prov = - ThisProvider::::with(token.trim_matches(char::from(0))); - let key = session.need_with::(&prov, |key| Ok(Box::from(key.as_slice())))?; - self.key_info = Some(KeyInfo { key_id: 0x01, key }); + let authid = String::from_utf8(data) + .unwrap() + .trim_matches(char::from(0)) + .to_string(); + let prov = ThisProvider::::with(&authid); + let key = session + .need_with::(&prov, |key| { + Ok(Box::from(key.as_slice())) + })?; + self.key_info = Some(KeyInfo { + authid, + key_id: 0x01, + key, + }); } None => { tracing::error!("No data in response"); @@ -679,6 +690,25 @@ impl Authentication for FabFire { writer .write_all(&send_buf) .map_err(|e| SessionError::Io { source: e })?; + + struct Prov<'a> { + authid: &'a str, + } + impl<'a> Provider<'a> for Prov<'a> { + fn provide( + &self, + req: &mut Demand<'a>, + ) -> DemandReply<()> + { + req.provide_ref::(self.authid)? + .done() + } + } + let prov = Prov { + authid: &self.key_info.as_ref().unwrap().authid, + }; + session.validate(&prov)?; + return Ok(State::Finished(MessageSent::Yes)); } Err(e) => { diff --git a/bffhd/authentication/fabfire_bin/mod.rs b/bffhd/authentication/fabfire_bin/mod.rs new file mode 100644 index 0000000..87e74d6 --- /dev/null +++ b/bffhd/authentication/fabfire_bin/mod.rs @@ -0,0 +1,11 @@ +mod server; +pub use server::FabFire; + +use rsasl::mechname::Mechname; +use rsasl::registry::{Mechanism, Side, MECHANISMS}; + +const MECHNAME: &'static Mechname = &Mechname::const_new_unchecked(b"X-FABFIRE-BIN"); + +#[linkme::distributed_slice(MECHANISMS)] +pub static FABFIRE: Mechanism = + Mechanism::build(MECHNAME, 300, None, Some(FabFire::new_server), Side::Client); diff --git a/bffhd/authentication/fabfire_bin/server.rs b/bffhd/authentication/fabfire_bin/server.rs new file mode 100644 index 0000000..db563ce --- /dev/null +++ b/bffhd/authentication/fabfire_bin/server.rs @@ -0,0 +1,526 @@ +use desfire::desfire::desfire::MAX_BYTES_PER_TRANSACTION; +use desfire::desfire::Desfire; +use desfire::error::Error as DesfireError; +use desfire::iso7816_4::apduresponse::APDUResponse; +use rsasl::callback::SessionData; +use rsasl::mechanism::{ + Authentication, Demand, DemandReply, MechanismData, MechanismError, MechanismErrorKind, + Provider, State, ThisProvider, +}; +use rsasl::prelude::{MessageSent, SASLConfig, SASLError, SessionError}; +use rsasl::property::AuthId; +use serde::{Deserialize, Serialize}; +use std::convert::TryFrom; +use std::fmt::{Debug, Display, Formatter}; +use std::io::Write; +use std::sync::Arc; + +use crate::authentication::fabfire::FabFireCardKey; + +enum FabFireError { + ParseError, + SerializationError, + DeserializationError(serde_json::Error), + CardError(DesfireError), + InvalidMagic(String), + InvalidToken(String), + InvalidURN(String), + InvalidCredentials(String), + Session(SessionError), +} + +impl Debug for FabFireError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FabFireError::ParseError => write!(f, "ParseError"), + FabFireError::SerializationError => write!(f, "SerializationError"), + FabFireError::DeserializationError(e) => write!(f, "DeserializationError: {}", e), + FabFireError::CardError(err) => write!(f, "CardError: {}", err), + FabFireError::InvalidMagic(magic) => write!(f, "InvalidMagic: {}", magic), + FabFireError::InvalidToken(token) => write!(f, "InvalidToken: {}", token), + FabFireError::InvalidURN(urn) => write!(f, "InvalidURN: {}", urn), + FabFireError::InvalidCredentials(credentials) => { + write!(f, "InvalidCredentials: {}", credentials) + } + FabFireError::Session(err) => write!(f, "Session: {}", err), + } + } +} + +impl Display for FabFireError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + FabFireError::ParseError => write!(f, "ParseError"), + FabFireError::SerializationError => write!(f, "SerializationError"), + FabFireError::DeserializationError(e) => write!(f, "DeserializationError: {}", e), + FabFireError::CardError(err) => write!(f, "CardError: {}", err), + FabFireError::InvalidMagic(magic) => write!(f, "InvalidMagic: {}", magic), + FabFireError::InvalidToken(token) => write!(f, "InvalidToken: {}", token), + FabFireError::InvalidURN(urn) => write!(f, "InvalidURN: {}", urn), + FabFireError::InvalidCredentials(credentials) => { + write!(f, "InvalidCredentials: {}", credentials) + } + FabFireError::Session(err) => write!(f, "Session: {}", err), + } + } +} + +impl std::error::Error for FabFireError {} + +impl MechanismError for FabFireError { + fn kind(&self) -> MechanismErrorKind { + match self { + FabFireError::ParseError => MechanismErrorKind::Parse, + FabFireError::SerializationError => MechanismErrorKind::Protocol, + FabFireError::DeserializationError(_) => MechanismErrorKind::Parse, + FabFireError::CardError(_) => MechanismErrorKind::Protocol, + FabFireError::InvalidMagic(_) => MechanismErrorKind::Protocol, + FabFireError::InvalidToken(_) => MechanismErrorKind::Protocol, + FabFireError::InvalidURN(_) => MechanismErrorKind::Protocol, + FabFireError::InvalidCredentials(_) => MechanismErrorKind::Protocol, + FabFireError::Session(_) => MechanismErrorKind::Protocol, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +struct CardInfo { + #[serde(rename = "UID", with = "hex")] + uid: [u8; 7], + key_old: Option>, + key_new: Option>, +} + +struct KeyInfo { + authid: String, + key_id: u8, + key: Box<[u8]>, +} + +struct AuthInfo { + rnd_a: Vec, + rnd_b: Vec, + iv: Vec, +} + +enum Step { + New, + SelectApp, + VerifyMagic, + GetURN, + GetToken, + Authenticate1, + Authenticate2, +} + +pub struct FabFire { + step: Step, + card_info: Option, + key_info: Option, + auth_info: Option, + app_id: u32, + local_urn: String, + desfire: Desfire, +} + +const MAGIC: &'static str = "FABACCESS\0DESFIRE\01.0\0"; + +impl FabFire { + pub fn new_server(_sasl: &SASLConfig) -> Result, SASLError> { + Ok(Box::new(Self { + step: Step::New, + card_info: None, + key_info: None, + auth_info: None, + app_id: 0x464142, + local_urn: "urn:fabaccess:lab:innovisionlab".to_string(), + desfire: Desfire { + card: None, + session_key: None, + cbc_iv: None, + }, + })) + } +} + +impl Authentication for FabFire { + fn step( + &mut self, + session: &mut MechanismData<'_>, + input: Option<&[u8]>, + writer: &mut dyn Write, + ) -> Result { + match self.step { + Step::New => { + tracing::trace!("Step: New"); + //receive card info (especially card UID) from reader + return match input { + None => Err(SessionError::InputDataRequired), + Some(_) => { + //select application + return match self.desfire.select_application_cmd(self.app_id) { + Ok(buf) => match Vec::::try_from(buf) { + Ok(data) => { + self.step = Step::SelectApp; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!( + "Failed to convert APDUCommand to Vec: {:?}", + e + ); + return Err(FabFireError::SerializationError.into()); + } + }, + Err(e) => { + tracing::error!("Failed to generate APDUCommand: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }; + } + }; + } + Step::SelectApp => { + tracing::trace!("Step: SelectApp"); + // check that we successfully selected the application + + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + apdu_response + .check() + .map_err(|e| FabFireError::CardError(e))?; + + // request the contents of the file containing the magic string + const MAGIC_FILE_ID: u8 = 0x01; + + return match self + .desfire + .read_data_chunk_cmd(MAGIC_FILE_ID, 0, MAGIC.len()) + { + Ok(buf) => match Vec::::try_from(buf) { + Ok(data) => { + self.step = Step::VerifyMagic; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!("Failed to convert APDUCommand to Vec: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }, + Err(e) => { + tracing::error!("Failed to generate APDUCommand: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }; + } + Step::VerifyMagic => { + tracing::trace!("Step: VerifyMagic"); + // verify the magic string to determine that we have a valid fabfire card + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + match apdu_response.check() { + Ok(_) => { + match apdu_response.body { + Some(data) => { + if std::str::from_utf8(data.as_slice()) != Ok(MAGIC) { + tracing::error!("Invalid magic string"); + return Err(FabFireError::ParseError.into()); + } + } + None => { + tracing::error!("No data returned from card"); + return Err(FabFireError::ParseError.into()); + } + }; + } + Err(e) => { + tracing::error!("Got invalid APDUResponse: {:?}", e); + return Err(FabFireError::ParseError.into()); + } + } + + // request the contents of the file containing the URN + const URN_FILE_ID: u8 = 0x02; + + return match self.desfire.read_data_chunk_cmd( + URN_FILE_ID, + 0, + self.local_urn.as_bytes().len(), + ) { + // TODO: support urn longer than 47 Bytes + Ok(buf) => match Vec::::try_from(buf) { + Ok(data) => { + self.step = Step::GetURN; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!("Failed to convert APDUCommand to Vec: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }, + Err(e) => { + tracing::error!("Failed to generate APDUCommand: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }; + } + Step::GetURN => { + tracing::trace!("Step: GetURN"); + // parse the urn and match it to our local urn + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + match apdu_response.check() { + Ok(_) => { + match apdu_response.body { + Some(data) => { + let received_urn = String::from_utf8(data).unwrap(); + if received_urn != self.local_urn { + tracing::error!( + "URN mismatch: {:?} != {:?}", + received_urn, + self.local_urn + ); + return Err(FabFireError::ParseError.into()); + } + } + None => { + tracing::error!("No data returned from card"); + return Err(FabFireError::ParseError.into()); + } + }; + } + Err(e) => { + tracing::error!("Got invalid APDUResponse: {:?}", e); + return Err(FabFireError::ParseError.into()); + } + } + // request the contents of the file containing the URN + const TOKEN_FILE_ID: u8 = 0x03; + + return match self.desfire.read_data_chunk_cmd( + TOKEN_FILE_ID, + 0, + MAX_BYTES_PER_TRANSACTION, + ) { + // TODO: support data longer than 47 Bytes + Ok(buf) => match Vec::::try_from(buf) { + Ok(data) => { + self.step = Step::GetToken; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!("Failed to convert APDUCommand to Vec: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }, + Err(e) => { + tracing::error!("Failed to generate APDUCommand: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }; + } + Step::GetToken => { + // println!("Step: GetToken"); + // parse the token and select the appropriate user + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + match apdu_response.check() { + Ok(_) => { + match apdu_response.body { + Some(data) => { + let authid = String::from_utf8(data) + .unwrap() + .trim_matches(char::from(0)) + .to_string(); + let prov = ThisProvider::::with(&authid); + let key = session + .need_with::(&prov, |key| { + Ok(Box::from(key.as_slice())) + })?; + self.key_info = Some(KeyInfo { + authid, + key_id: 0x01, + key, + }); + } + None => { + tracing::error!("No data in response"); + return Err(FabFireError::ParseError.into()); + } + }; + } + Err(e) => { + tracing::error!("Failed to check response: {:?}", e); + return Err(FabFireError::ParseError.into()); + } + } + + return match self + .desfire + .authenticate_iso_aes_challenge_cmd(self.key_info.as_ref().unwrap().key_id) + { + Ok(buf) => match Vec::::try_from(buf) { + Ok(data) => { + self.step = Step::Authenticate1; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!("Failed to convert to Vec: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }, + Err(e) => { + tracing::error!("Failed to create authenticate command: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + }; + } + Step::Authenticate1 => { + tracing::trace!("Step: Authenticate1"); + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + return match apdu_response.check() { + Ok(_) => { + match apdu_response.body { + Some(data) => { + let rnd_b_enc = data.as_slice(); + + //FIXME: This is ugly, we should find a better way to make the function testable + //TODO: Check if we need a CSPRNG here + let rnd_a: [u8; 16] = rand::random(); + + let (cmd_challenge_response, rnd_b, iv) = self + .desfire + .authenticate_iso_aes_response_cmd( + rnd_b_enc, + &*(self.key_info.as_ref().unwrap().key), + &rnd_a, + ) + .unwrap(); + self.auth_info = Some(AuthInfo { + rnd_a: Vec::::from(rnd_a), + rnd_b, + iv, + }); + match Vec::::try_from(cmd_challenge_response) { + Ok(data) => { + self.step = Step::Authenticate2; + writer + .write_all(&data) + .map_err(|e| SessionError::Io { source: e })?; + Ok(State::Running) + } + Err(e) => { + tracing::error!("Failed to convert to Vec: {:?}", e); + return Err(FabFireError::SerializationError.into()); + } + } + } + None => { + tracing::error!("Got invalid response: {:?}", apdu_response); + Err(FabFireError::ParseError.into()) + } + } + } + Err(e) => { + tracing::error!("Failed to check response: {:?}", e); + Err(FabFireError::ParseError.into()) + } + }; + } + Step::Authenticate2 => { + // println!("Step: Authenticate2"); + let apdu_response = match input { + Some(data) => APDUResponse::new(data), + None => return Err(SessionError::InputDataRequired), + }; + + match apdu_response.check() { + Ok(_) => { + match apdu_response.body { + Some(data) => match self.auth_info.as_ref() { + None => { + return Err(FabFireError::ParseError.into()); + } + Some(auth_info) => { + if self + .desfire + .authenticate_iso_aes_verify( + data.as_slice(), + auth_info.rnd_a.as_slice(), + auth_info.rnd_b.as_slice(), + &*(self.key_info.as_ref().unwrap().key), + auth_info.iv.as_slice(), + ) + .is_ok() + { + struct Prov<'a> { + authid: &'a str, + } + impl<'a> Provider<'a> for Prov<'a> { + fn provide( + &self, + req: &mut Demand<'a>, + ) -> DemandReply<()> + { + req.provide_ref::(self.authid)?.done() + } + } + let prov = Prov { + authid: &self.key_info.as_ref().unwrap().authid, + }; + session.validate(&prov)?; + return Ok(State::Finished(MessageSent::Yes)); + } + } + }, + None => { + tracing::error!("got empty response"); + return Err(FabFireError::ParseError.into()); + } + }; + } + Err(_e) => { + tracing::error!("Got invalid response: {:?}", apdu_response); + return Err( + FabFireError::InvalidCredentials(format!("{}", apdu_response)).into(), + ); + } + } + } + } + + return Ok(State::Finished(MessageSent::No)); + } +} diff --git a/bffhd/authentication/mod.rs b/bffhd/authentication/mod.rs index 68ff703..6ec5c8d 100644 --- a/bffhd/authentication/mod.rs +++ b/bffhd/authentication/mod.rs @@ -1,16 +1,17 @@ use crate::users::Users; use miette::{IntoDiagnostic, WrapErr}; -use std::sync::Arc; -use rsasl::callback::{CallbackError, Request, SessionCallback, SessionData, Context}; +use rsasl::callback::{CallbackError, Context, Request, SessionCallback, SessionData}; use rsasl::mechanism::SessionError; use rsasl::prelude::{Mechname, SASLConfig, SASLServer, Session, Validation}; use rsasl::property::{AuthId, AuthzId, Password}; use rsasl::validate::{Validate, ValidationError}; +use std::sync::Arc; use crate::authentication::fabfire::FabFireCardKey; use crate::users::db::User; mod fabfire; +mod fabfire_bin; struct Callback { users: Users, @@ -23,41 +24,55 @@ impl Callback { } } impl SessionCallback for Callback { - fn callback(&self, session_data: &SessionData, context: &Context, request: &mut Request) -> Result<(), SessionError> { + fn callback( + &self, + session_data: &SessionData, + context: &Context, + request: &mut Request, + ) -> Result<(), SessionError> { if let Some(authid) = context.get_ref::() { request.satisfy_with::(|| { let user = self.users.get_user(authid).ok_or(CallbackError::NoValue)?; - let kv = user.userdata.kv.get("cardkey").ok_or(CallbackError::NoValue)?; - let card_key = <[u8; 16]>::try_from( - hex::decode(kv).map_err(|_| CallbackError::NoValue)?, - ).map_err(|_| CallbackError::NoValue)?; + let kv = user + .userdata + .kv + .get("cardkey") + .ok_or(CallbackError::NoValue)?; + let card_key = + <[u8; 16]>::try_from(hex::decode(kv).map_err(|_| CallbackError::NoValue)?) + .map_err(|_| CallbackError::NoValue)?; Ok(card_key) })?; } Ok(()) } - fn validate(&self, session_data: &SessionData, context: &Context, validate: &mut Validate<'_>) -> Result<(), ValidationError> { + fn validate( + &self, + session_data: &SessionData, + context: &Context, + validate: &mut Validate<'_>, + ) -> Result<(), ValidationError> { let span = tracing::info_span!(parent: &self.span, "validate"); let _guard = span.enter(); if validate.is::() { match session_data.mechanism().mechanism.as_str() { "PLAIN" => { - let authcid = context.get_ref::() + let authcid = context + .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; let authzid = context.get_ref::(); - let password = context.get_ref::() + let password = context + .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; - if authzid.is_some() { - return Ok(()) - } + // if authzid.is_some() { + // return Ok(()) + // } if let Some(user) = self.users.get_user(authcid) { match user.check_password(password) { - Ok(true) => { - validate.finalize::(user) - } + Ok(true) => validate.finalize::(user), Ok(false) => { tracing::warn!(authid=%authcid, "AUTH FAILED: bad password"); } @@ -69,6 +84,14 @@ impl SessionCallback for Callback { tracing::warn!(authid=%authcid, "AUTH FAILED: no such user"); } } + "X-FABFIRE" | "X-FABFIRE-BIN" => { + let authcid = context + .get_ref::() + .ok_or(ValidationError::MissingRequiredProperty)?; + if let Some(user) = self.users.get_user(authcid) { + validate.finalize::(user) + } + } _ => {} } } diff --git a/bffhd/capnp/authenticationsystem.rs b/bffhd/capnp/authenticationsystem.rs index 943933c..c81f547 100644 --- a/bffhd/capnp/authenticationsystem.rs +++ b/bffhd/capnp/authenticationsystem.rs @@ -2,21 +2,21 @@ use capnp::capability::Promise; use capnp::Error; use capnp_rpc::pry; use rsasl::mechname::Mechname; +use rsasl::prelude::State as SaslState; +use rsasl::prelude::{MessageSent, Session}; use rsasl::property::AuthId; use std::fmt; use std::fmt::{Formatter, Write}; use std::io::Cursor; -use rsasl::prelude::{MessageSent, Session}; -use rsasl::prelude::State as SaslState; use tracing::Span; +use crate::authentication::V; use crate::capnp::session::APISession; use crate::session::SessionManager; use api::authenticationsystem_capnp::authentication::{ AbortParams, AbortResults, Server as AuthenticationSystem, StepParams, StepResults, }; use api::authenticationsystem_capnp::{response, response::Error as ErrorCode}; -use crate::authentication::V; const TARGET: &str = "bffh::api::authenticationsystem"; diff --git a/bffhd/initiators/process.rs b/bffhd/initiators/process.rs index d714ec3..79f6bde 100644 --- a/bffhd/initiators/process.rs +++ b/bffhd/initiators/process.rs @@ -1,5 +1,6 @@ use super::Initiator; use super::InitiatorCallbacks; +use crate::resources::modules::fabaccess::Status; use crate::resources::state::State; use crate::utils::linebuffer::LineBuffer; use async_process::{Child, ChildStderr, ChildStdout, Command, Stdio}; @@ -11,7 +12,6 @@ use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::resources::modules::fabaccess::Status; #[derive(Debug, Serialize, Deserialize)] pub enum InputMessage { @@ -63,7 +63,12 @@ struct ProcessState { impl ProcessState { pub fn new(stdout: ChildStdout, stderr: ChildStderr, child: Child) -> Self { - Self { stdout, stderr, stderr_closed: false, child } + Self { + stdout, + stderr, + stderr_closed: false, + child, + } } fn try_process(&mut self, buffer: &[u8], callbacks: &mut InitiatorCallbacks) -> usize { @@ -100,7 +105,9 @@ impl ProcessState { let InputMessage::SetState(status) = state; callbacks.set_status(status); } - Err(error) => tracing::warn!(%error, "process initiator did not send a valid line"), + Err(error) => { + tracing::warn!(%error, "process initiator did not send a valid line") + } } } } @@ -202,8 +209,8 @@ impl Future for Process { impl Initiator for Process { fn new(params: &HashMap, callbacks: InitiatorCallbacks) -> miette::Result - where - Self: Sized, + where + Self: Sized, { let cmd = params .get("cmd") diff --git a/bffhd/session/mod.rs b/bffhd/session/mod.rs index dee72a6..2ac2be7 100644 --- a/bffhd/session/mod.rs +++ b/bffhd/session/mod.rs @@ -1,10 +1,10 @@ use crate::authorization::permissions::Permission; use crate::authorization::roles::Roles; use crate::resources::Resource; +use crate::users::db::User; use crate::users::{db, UserRef}; use crate::Users; use tracing::Span; -use crate::users::db::User; #[derive(Clone)] pub struct SessionManager { @@ -18,7 +18,9 @@ impl SessionManager { } pub fn try_open(&self, parent: &Span, uid: impl AsRef) -> Option { - self.users.get_user(uid.as_ref()).map(|user| self.open(parent, user)) + self.users + .get_user(uid.as_ref()) + .map(|user| self.open(parent, user)) } // TODO: make infallible