diff --git a/.env.example b/.env.example index 87fa44e..6e2535e 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,11 @@ CONTINUUM_UPLINK_URL=wss://api.continuum.local/ws/edge/v1 CONTINUUM_GO2RTC_BIN=/usr/local/bin/go2rtc CONTINUUM_GO2RTC_CONFIG=./go2rtc.yaml +# Bambu MQTTS certificate trust. Most printers verify fine against the +# bundled shared CA (certs/bambu_ca2.pem) and need nothing here. For a +# printer that doesn't chain to it (P1P, at least), add "printer_id=path" +# entries, comma-separated: CONTINUUM_BAMBU_CA_CERT_OVERRIDES=p1=./certs/p1p.pem +CONTINUUM_BAMBU_CA_CERT_OVERRIDES= +CONTINUUM_BAMBU_REQUIRE_VALID_CERT=true + RUST_LOG=info,continuum_proxy=debug diff --git a/certs/README.md b/certs/README.md index 88df54c..592ce68 100644 --- a/certs/README.md +++ b/certs/README.md @@ -14,10 +14,16 @@ instead of fetching/pinning a certificate per printer. `Basic Constraints: CA:TRUE`, `Key Usage: Certificate Sign, CRL Sign` — a genuine root CA, not a per-device leaf certificate. Valid until 2050. -The "CA2" name implies there was a CA1 generation before it — some -older printers/firmware may not chain to this root and need their own -certificate instead. `CONTINUUM_BAMBU_CA_CERT_PATH` (see `.env.example`) -overrides this default with a specific file for exactly that case. +The "CA2" name implies there was a CA1 generation before it, and at least +one current model — P1P — doesn't chain to this root either: BambuStudio's +`resources/cert/` only has this one file (plus an unrelated +`*.bambulab.com` leaf cert for their cloud API), so there's no second +bundled file to grab for P1P. That means it's a genuine per-device +certificate, the same "download it from the printer" case as the older +units. `CONTINUUM_BAMBU_CA_CERT_OVERRIDES` (see `.env.example`) is a +per-printer-id map for exactly this — there's no single fleet-wide +override, because different printers can legitimately need different +certificates at the same time. If a printer's connection ever fails certificate verification against this file, re-fetch from the source URL above (BambuStudio ships whatever the diff --git a/src/config.rs b/src/config.rs index 2845b33..7428e8d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::env; use std::path::PathBuf; @@ -10,14 +11,17 @@ pub struct Config { pub go2rtc_bin: String, pub go2rtc_config: String, - /// Overrides the bundled Bambu root CA (see `certs/bambu_ca2.pem`) with - /// a specific certificate file — for a printer whose firmware doesn't - /// chain to that shared CA. Most setups leave this unset. - pub bambu_ca_cert_path: Option, + /// Per-printer CA certificate overrides, keyed by printer ID. Most + /// Bambu printers chain to the bundled shared CA (`certs/bambu_ca2.pem`) + /// and don't need an entry here — but some models (P1P, at least) ship + /// their own certificate instead of one signed by that shared CA, with + /// no single file covering all of them. Look up a printer's own entry + /// (if any) when constructing it; there's no fleet-wide "the" override. + pub bambu_ca_cert_overrides: HashMap, /// When `false`, Bambu MQTTS connections skip certificate verification - /// entirely instead of checking against the CA above. `true` (the - /// default) is the secure choice; only flip this for a printer whose - /// certificate you can't get to verify any other way. + /// entirely instead of checking against a CA. `true` (the default) is + /// the secure choice; only flip this fleet-wide policy if you have + /// printers you can't get to verify any other way. pub bambu_require_valid_cert: bool, } @@ -31,12 +35,21 @@ impl Config { go2rtc_bin: env_or("CONTINUUM_GO2RTC_BIN", "go2rtc"), go2rtc_config: env_or("CONTINUUM_GO2RTC_CONFIG", "./go2rtc.yaml"), - bambu_ca_cert_path: env::var("CONTINUUM_BAMBU_CA_CERT_PATH").ok().map(PathBuf::from), + bambu_ca_cert_overrides: parse_cert_overrides(&env_or("CONTINUUM_BAMBU_CA_CERT_OVERRIDES", "")), bambu_require_valid_cert: env_or("CONTINUUM_BAMBU_REQUIRE_VALID_CERT", "true") == "true", }) } } +/// Parses `"printer_id=path,printer_id2=path2"` into a lookup map. Empty +/// input (the common case — no overrides needed) yields an empty map. +fn parse_cert_overrides(raw: &str) -> HashMap { + raw.split(',') + .filter_map(|entry| entry.split_once('=')) + .map(|(id, path)| (id.trim().to_string(), PathBuf::from(path.trim()))) + .collect() +} + fn require(key: &str) -> anyhow::Result { env::var(key).map_err(|_| anyhow::anyhow!("missing required env var {key}")) } diff --git a/src/printer/bambu.rs b/src/printer/bambu.rs index e0a39d7..d36531f 100644 --- a/src/printer/bambu.rs +++ b/src/printer/bambu.rs @@ -24,10 +24,13 @@ pub enum BambuTls { } impl BambuTls { - /// Turns `Config`'s two raw settings into one of the three states - /// above. `require_valid_cert = false` always means `Insecure`, - /// regardless of whether a custom cert path was also given. - pub fn from_config(cert_path: Option<&Path>, require_valid_cert: bool) -> anyhow::Result { + /// Resolves the trust mode for *one* printer. Call this per printer, + /// passing `config.bambu_ca_cert_overrides.get(printer_id)` — there's + /// no single override for the whole fleet, because not every Bambu + /// model chains to the same CA (P1P doesn't). `require_valid_cert = + /// false` always means `Insecure`, regardless of whether that printer + /// also has an override entry. + pub fn resolve(cert_path: Option<&Path>, require_valid_cert: bool) -> anyhow::Result { if !require_valid_cert { return Ok(BambuTls::Insecure); }