enrolment carries the CA, and one store holds it on every platform
An APK built in this VM pins this VM's CA, so it can never reach the host's ai-server -- which is exactly the iris Android client's situation (cross-compiled here, run against the host). So ai-server now puts the CA in every enrollment link it mints, base64url of its DER under the 'ca' parameter wg-app-link just learned to add, and client_core parses it back out as PEM. Nothing has to be built on the machine it talks to. Refused rather than ignored where 'ca' does not decode: a link that named a certificate and then pinned nothing is the one outcome nothing downstream could notice. EnrollmentStore moves out of desktop-app into client_core::config, since the Android client needs the same file for the same reason and only the directory differs by platform (AGENTS.md's sharing rule). desktop-app's --ca becomes the override for a link that carried none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b27e858b5
commit
ade572973a
9 files changed
+325
-155
No files matched your search
+44
-5
@@ -120,6 +120,35 @@ struct Args {
|
||||
throwaway_sessions: bool,
|
||||
}
|
||||
|
||||
/// Where this run keeps its certificates: `--certs`, else the XDG default.
|
||||
/// One reader because `--enroll-link` returns before the rest of startup
|
||||
/// gets there, and a link minted against a different directory's CA than
|
||||
/// the server presents is a handshake failure with nothing on screen
|
||||
/// saying why.
|
||||
fn certs_dir(certs: &Option<std::path::PathBuf>) -> std::path::PathBuf {
|
||||
certs
|
||||
.clone()
|
||||
.unwrap_or_else(|| config_home("ai-app").join("certs"))
|
||||
}
|
||||
|
||||
/// The CA every enrollment link carries (`wg_app_link::enroll::ca_param`),
|
||||
/// so an app that was not built on this machine can still pin it -- the
|
||||
/// iris client is cross-compiled in a VM and run against this server.
|
||||
///
|
||||
/// `--enroll-link` reads it before the server has been anywhere near
|
||||
/// `certs::ensure`, so the file may genuinely not exist yet; the message
|
||||
/// says what makes it exist rather than reporting a bare ENOENT.
|
||||
fn read_ca(certs_dir: &std::path::Path) -> Result<String> {
|
||||
let path = certs_dir.join("ca.pem");
|
||||
std::fs::read_to_string(&path).with_context(|| {
|
||||
format!(
|
||||
"no CA certificate at {} -- start ai-server once so it generates one, \
|
||||
or point --certs at the directory that has it",
|
||||
path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// Both rustls crypto providers are in the dependency graph (ureq brings
|
||||
@@ -160,7 +189,13 @@ async fn main() -> Result<()> {
|
||||
)?;
|
||||
println!(
|
||||
"{}",
|
||||
enroll::enrollment_uri("aiapp", bind_ip, args.port, &token)
|
||||
enroll::enrollment_uri(
|
||||
"aiapp",
|
||||
bind_ip,
|
||||
args.port,
|
||||
&token,
|
||||
Some(&read_ca(&certs_dir(&args.certs))?)
|
||||
)?
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
@@ -216,9 +251,7 @@ async fn main() -> Result<()> {
|
||||
// Before the interface check below, deliberately: the certificates are also
|
||||
// what the phone app embeds at build time, so they need to be obtainable on
|
||||
// a machine whose tunnel isn't up yet. The leaf is reissued on every start.
|
||||
let certs_dir = args
|
||||
.certs
|
||||
.unwrap_or_else(|| config_home("ai-app").join("certs"));
|
||||
let certs_dir = certs_dir(&args.certs);
|
||||
let certificates = wg_app_link::certs::ensure("ai-app", &certs_dir, &netif::local_addresses())
|
||||
.with_context(|| format!("failed to prepare certificates in {}", certs_dir.display()))?;
|
||||
if certificates.ca_is_new {
|
||||
@@ -252,7 +285,13 @@ async fn main() -> Result<()> {
|
||||
if rotating {
|
||||
tracing::info!("rotated the enrolled token; the previous one is now invalid");
|
||||
}
|
||||
enroll::print_enrollment("aiapp", bind_ip, args.port, &token)?;
|
||||
enroll::print_enrollment(
|
||||
"aiapp",
|
||||
bind_ip,
|
||||
args.port,
|
||||
&token,
|
||||
Some(&read_ca(&certs_dir)?),
|
||||
)?;
|
||||
}
|
||||
|
||||
let tls_config = axum_server::tls_rustls::RustlsConfig::from_pem_file(
|
||||
|
||||
Reference in new issue
Block a user