diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 096c8db..eaf56d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,6 +123,12 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@1583d6b38d7be47f593cb472781bbb21cab4321e # v0.0.10 + - name: Install build dependencies + run: | + sudo dpkg --add-architecture arm64 + sudo apt-get update + sudo apt-get -y install pkg-config libudev-dev libudev-dev:arm64 + - name: Build Linux x86_64 binary run: | cargo build --locked --release --target x86_64-unknown-linux-gnu @@ -133,7 +139,8 @@ jobs: - name: Build Linux aarch64 binary env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - PKG_CONFIG_SYSROOT_DIR: /usr/lib/aarch64-linux-gnu + PKG_CONFIG_ALLOW_CROSS: "1" + PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig run: | cargo build --locked --release --target aarch64-unknown-linux-gnu mv target/aarch64-unknown-linux-gnu/release/defguard-proxy defguard-proxy-${{ env.VERSION }}-aarch64-unknown-linux-gnu diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 85cbae9..15d3d20 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,8 +52,8 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@1583d6b38d7be47f593cb472781bbb21cab4321e # v0.0.10 - - name: Install protoc - run: apt-get update && apt-get -y install protobuf-compiler + - name: Install protoc and build dependencies + run: apt-get update && apt-get -y install protobuf-compiler pkg-config libudev-dev - name: Check format run: | diff --git a/Dockerfile b/Dockerfile index 0dd91de..0d3aeac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ FROM rust:1 AS chef WORKDIR /build # install & cache necessary components +RUN apt-get update && apt-get -y install pkg-config libudev-dev && rm -rf /var/lib/apt/lists/* RUN cargo install cargo-chef RUN rustup component add rustfmt @@ -42,7 +43,7 @@ FROM debian:13-slim AS runtime # Bust the cache for the layer below on every build so OS security updates are always applied. ARG CACHEBUST=0 RUN echo "cachebust=${CACHEBUST}" && apt-get update -y && apt-get upgrade -y && \ - apt-get install --no-install-recommends -y ca-certificates libssl-dev lsb-release && \ + apt-get install --no-install-recommends -y ca-certificates libssl-dev libudev1 lsb-release && \ rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=builder /build/bin/defguard-proxy . diff --git a/build.rs b/build.rs index b6c53e5..ee65df4 100644 --- a/build.rs +++ b/build.rs @@ -32,6 +32,12 @@ fn main() -> Result<(), Box> { "ClientMfaStartRequest.selected_methods", "#[serde(default)]", ) + // Sent only when setting up FIDO2, absent from code-factor request bodies. + .field_attribute("CodeMfaSetupFinishRequest.name", "#[serde(default)]") + .field_attribute( + "CodeMfaSetupFinishRequest.fido2_attestation", + "#[serde(default)]", + ) // Protobuf enum values carry the enum name prefix to avoid package-scope // collisions, so the generated Rust variants all share a prefix that clippy // flags. Suppress it on the generated type. diff --git a/proto b/proto index 64dccb0..c7bda9d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 64dccb0073538ff3f1e4a5ec5051465f7de7d8e4 +Subproject commit c7bda9d94d4f1906331e0d1d21af197a40ecc160 diff --git a/src/handlers/register_mfa.rs b/src/handlers/register_mfa.rs index d241e39..f30a48e 100644 --- a/src/handlers/register_mfa.rs +++ b/src/handlers/register_mfa.rs @@ -18,7 +18,7 @@ pub(crate) fn router() -> Router { .route("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/code/finish", post(register_code_mfa_finish)) } -/// Forwards a code MFA setup start to Core. +/// Forwards an MFA factor setup start to Core. /// /// `req.token` is either an enrollment token or an authorized MFA config session token. pub(super) async fn code_mfa_setup_start( @@ -26,8 +26,8 @@ pub(super) async fn code_mfa_setup_start( device_info: DeviceInfo, req: CodeMfaSetupStartRequest, ) -> Result, ApiError> { - debug!("Code MFA setup started"); - reject_non_code_method(req.method)?; + debug!("MFA factor setup started"); + reject_unsupported_method(req.method)?; let rx = state .grpc_server @@ -39,13 +39,13 @@ pub(super) async fn code_mfa_setup_start( } } -/// Forwards a code MFA setup finish to Core. See [`code_mfa_setup_start`] for the token. +/// Forwards an MFA factor setup finish to Core. See [`code_mfa_setup_start`] for the token. pub(super) async fn code_mfa_setup_finish( state: &AppState, device_info: DeviceInfo, req: CodeMfaSetupFinishRequest, ) -> Result, ApiError> { - reject_non_code_method(req.method)?; + reject_unsupported_method(req.method)?; let rx = state .grpc_server @@ -57,9 +57,11 @@ pub(super) async fn code_mfa_setup_finish( } } -/// Code MFA setup only knows how to deliver a code by email or TOTP. -fn reject_non_code_method(method: i32) -> Result<(), ApiError> { - if method == MfaMethod::Email as i32 || method == MfaMethod::Totp as i32 { +fn reject_unsupported_method(method: i32) -> Result<(), ApiError> { + if matches!( + MfaMethod::try_from(method), + Ok(MfaMethod::Email | MfaMethod::Totp | MfaMethod::Fido2) + ) { Ok(()) } else { error!("Requested method not supported"); @@ -67,6 +69,17 @@ fn reject_non_code_method(method: i32) -> Result<(), ApiError> { } } +/// Enrollment routes carry no key name or attestation, so FIDO2 must go +/// through MFA configuration instead. +fn reject_non_code_method(method: MfaMethod) -> Result<(), ApiError> { + if matches!(method, MfaMethod::Email | MfaMethod::Totp) { + Ok(()) + } else { + error!("Requested method not supported during enrollment"); + Err(ApiError::BadRequest("Method not supported.".to_string())) + } +} + #[derive(Debug, Clone, Deserialize)] struct RegisterMfaCodeStartRequest { pub method: MfaMethod, @@ -80,6 +93,7 @@ async fn register_code_mfa_start( Json(req): Json, ) -> Result, ApiError> { let token = enrollment_token(&cookie_jar)?; + reject_non_code_method(req.method)?; code_mfa_setup_start( &state, device_info, @@ -105,6 +119,7 @@ async fn register_code_mfa_finish( Json(req): Json, ) -> Result, ApiError> { let token = enrollment_token(&cookie_jar)?; + reject_non_code_method(req.method)?; code_mfa_setup_finish( &state, device_info, @@ -112,6 +127,9 @@ async fn register_code_mfa_finish( token, code: req.code, method: req.method as i32, + // FIDO2 only, and rejected above. + name: None, + fido2_attestation: None, }, ) .await diff --git a/src/tests/mfa_config.rs b/src/tests/mfa_config.rs index d4f25a1..d5a7de3 100644 --- a/src/tests/mfa_config.rs +++ b/src/tests/mfa_config.rs @@ -45,6 +45,7 @@ fn fallback_then_totp( assert_eq!(req.code, "123456"); core_response::Payload::MfaConfigAuthorize(MfaConfigAuthorizeResponse { deadline_timestamp: 1_800_003_600, + recovery_codes: vec![], }) } core_request::Payload::CodeMfaSetupStart(req) => { @@ -52,6 +53,7 @@ fn fallback_then_totp( assert_eq!(req.method, TOTP); core_response::Payload::CodeMfaSetupStartResponse(CodeMfaSetupStartResponse { totp_secret: Some("JBSWY3DPEHPK3PXP".into()), + fido2_creation_challenge: None, }) } core_request::Payload::CodeMfaSetupFinish(req) => {