Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 .
Expand Down
6 changes: 6 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
"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.
Expand Down
2 changes: 1 addition & 1 deletion proto
34 changes: 26 additions & 8 deletions src/handlers/register_mfa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ pub(crate) fn router() -> Router<AppState> {
.route("/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(
state: &AppState,
device_info: DeviceInfo,
req: CodeMfaSetupStartRequest,
) -> Result<Json<CodeMfaSetupStartResponse>, 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
Expand All @@ -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<Json<CodeMfaSetupFinishResponse>, ApiError> {
reject_non_code_method(req.method)?;
reject_unsupported_method(req.method)?;

let rx = state
.grpc_server
Expand All @@ -57,16 +57,29 @@ 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");
Err(ApiError::BadRequest("Method not supported.".to_string()))
}
}

/// 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,
Expand All @@ -80,6 +93,7 @@ async fn register_code_mfa_start(
Json(req): Json<RegisterMfaCodeStartRequest>,
) -> Result<Json<CodeMfaSetupStartResponse>, ApiError> {
let token = enrollment_token(&cookie_jar)?;
reject_non_code_method(req.method)?;
code_mfa_setup_start(
&state,
device_info,
Expand All @@ -105,13 +119,17 @@ async fn register_code_mfa_finish(
Json(req): Json<RegisterMfaCodeFinishRequest>,
) -> Result<Json<CodeMfaSetupFinishResponse>, ApiError> {
let token = enrollment_token(&cookie_jar)?;
reject_non_code_method(req.method)?;
code_mfa_setup_finish(
&state,
device_info,
CodeMfaSetupFinishRequest {
token,
code: req.code,
method: req.method as i32,
// FIDO2 only, and rejected above.
name: None,
fido2_attestation: None,
},
)
.await
Expand Down
2 changes: 2 additions & 0 deletions src/tests/mfa_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ 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) => {
assert_eq!(req.token, SESSION_TOKEN);
assert_eq!(req.method, TOTP);
core_response::Payload::CodeMfaSetupStartResponse(CodeMfaSetupStartResponse {
totp_secret: Some("JBSWY3DPEHPK3PXP".into()),
fido2_creation_challenge: None,
})
}
core_request::Payload::CodeMfaSetupFinish(req) => {
Expand Down
Loading