new command `pijul client` for authenticating to a HTTP server

pmeunier
Apr 23, 2023, 8:06 AM
LZOGKBJXRQJKXHYNNENJFGNLP5SHIXGSV6HDB7UVOP7FSA5EUNCQC

Dependencies

  • [2] X2MMGGXQ Removing dependencies with CVE (old version of Nix)
  • [3] 2MLOE3FP Solving conflicts
  • [4] 44RUBHRE Only re-prove identity when credentials change
  • [5] MEK57BAD Optional user in ssh_remote, allows to fix the key proof in `pijul id ed`
  • [6] M37JTFEI Restorting SSH auth with a key
  • [7] 4HTHYIA3 Fixing HTTP download
  • [8] UMF6N7CZ Keyring 2.0
  • [9] 4KJ45IJL Implement new identity management
  • [10] KKNMDXAI Tweak identity subcommand
  • [11] FI3WFMTS Simplifying the locks
  • [12] MU5GSJAW Partial push and pull (WARNING: breaks the existing protocol)
  • [13] JL4WKA5P Implement the Sanakirja concurrency model in a cross-process way
  • [14] VBMXB443 Retrying if the HTTP connection drops while reading the body
  • [15] LYTVEPH3 Avoid cloning into an existing path
  • [16] GYXIF25T Proper parsing of URLs
  • [17] FBXYP7QM Forgot to add remote::http
  • [18] QL6K2ZM3 Tags
  • [19] TPEH2XNB 1.0.0-alpha.28, with Tokio 1.0
  • [20] OIOMXESD Better error handling in HTTP
  • [21] DO2Y5TY5 Tag synchronisation
  • [22] DWSAYGVE Update codebase to use new identity management
  • [23] 2K7JLB4Z No pager on Windows
  • [24] A3RM526Y Integrating identity malleability
  • [25] SN7AGY6S Making `pijul lock` robust to kill signals
  • [26] IQ4FCHPZ HTTP connections: pooling + retry on error
  • [27] SXEYMYF7 Fixing the bad changes in history (unfortunately, by rebooting).
  • [28] EEBKW7VT Keys and identities
  • [29] IIV3EL2X Cleanup, formatting, and fixing the Git feature
  • [30] YWL2K3P7 Removing the `Direction` argument in pijul::remote::Repository::remote
  • [31] I24UEJQL Various post-fire fixes
  • [32] 4OJWMSOW Fully replace crate::Identity
  • [33] VL7ZYKHB Running hooks through shell on Windows and Unix
  • [34] ZHABNS3S Canonicalize all paths
  • [35] 6FRPUHWK Fix identity tests
  • [36] VAPBIG46 Version bump
  • [37] H4AU6QRP New config for HTTP remotes
  • [*] ZDK3GNDB Tag transactions (including a massive refactoring of errors)
  • [*] TI7PCK7J Update `pijul/src/main.rs` to use new identity management
  • [*] SNZ3OAMC use native external subcommand support instead of hand-rolled one
  • [*] TEDGMEHF Introduce subcommand for completion-script generation.

Change contents

  • replacement in pijul/src/remote/ssh.rs at line 249
    [6.1493][6.1493:1701]()
    .with_prompt(format!("Password for encrypted private key"))
    .allow_empty_password(false)
    .interact()
    .unwrap();
    [6.1493]
    [6.1701]
    .with_prompt(format!("Password for encrypted private key"))
    .allow_empty_password(false)
    .interact()
    .unwrap();
  • replacement in pijul/src/remote/ssh.rs at line 256
    [6.1851][6.1851:1884]()
    continue
    [6.1851]
    [6.1884]
    continue;
  • edit in pijul/src/remote/mod.rs at line 1121
    [9.68736]
    [39.4650]
    pub async fn prove(&mut self, key: libpijul::key::SKey) -> Result<(), anyhow::Error> {
    match *self {
    RemoteRepo::Ssh(ref mut s) => s.prove(key).await,
    RemoteRepo::Http(ref mut h) => h.prove(key).await,
    RemoteRepo::None => unreachable!(),
    _ => Ok(()),
    }
    }
  • replacement in pijul/src/remote/http.rs at line 41
    [9.24470][9.259:314](),[9.259][9.259:314]()
    std::fs::create_dir_all(&path.parent().unwrap())?;
    [9.24470]
    [9.314]
    tokio::fs::create_dir_all(&path.parent().unwrap()).await.unwrap();
  • replacement in pijul/src/remote/http.rs at line 43
    [9.358][9.33:89]()
    let mut f = tokio::fs::File::create(&path_).await?;
    [9.358]
    [9.499]
    let mut f = tokio::fs::File::create(&path_).await.unwrap();
  • edit in pijul/src/remote/http.rs at line 75
    [9.2168]
    [9.2168]
    debug!("kv = {:?} {:?}", k, v);
  • replacement in pijul/src/remote/http.rs at line 92
    [9.849][9.849:896]()
    delay *= 2.;
    continue;
    [9.849]
    [9.1183]
    bail!("Server returned {}", res.status().as_u16())
  • replacement in pijul/src/remote/http.rs at line 126
    [9.1304][7.831:874]()
    debug!("renaming {:?} {:?}", c, done);
    [9.1304]
    [9.1304]
    debug!("renaming {:?} {:?} {:?} {:?}", c, path_, path, done);
  • replacement in pijul/src/remote/http.rs at line 130
    [9.24556][9.24556:24605]()
    std::fs::rename(&path_, &path)?;
    [9.24556]
    [9.24605]
    tokio::fs::rename(&path_, &path).await?;
  • replacement in pijul/src/remote/http.rs at line 133
    [9.24649][9.24649:24698]()
    std::fs::rename(&path_, &path)?;
    [9.24649]
    [9.24698]
    tokio::fs::rename(&path_, &path).await?;
  • replacement in pijul/src/remote/http.rs at line 141
    [9.1406][9.1406:1435]()
    const POOL_SIZE: usize = 20;
    [9.1406]
    [9.209]
    const POOL_SIZE: usize = 1;
  • edit in pijul/src/remote/http.rs at line 272
    [9.2480]
    [9.2480]
    debug!("kv = {:?} {:?}", k, v);
  • edit in pijul/src/remote/http.rs at line 325
    [9.2740]
    [9.2740]
    debug!("kv = {:?} {:?}", k, v);
  • edit in pijul/src/remote/http.rs at line 343
    [9.5994]
    [9.5994]
    debug!("l = {:?}", l);
  • edit in pijul/src/remote/http.rs at line 359
    [9.6474]
    [9.6474]
    debug!("done");
  • edit in pijul/src/remote/http.rs at line 384
    [9.2976]
    [9.2976]
    debug!("kv = {:?} {:?}", k, v);
  • edit in pijul/src/remote/http.rs at line 418
    [9.3212]
    [9.3212]
    debug!("kv = {:?} {:?}", k, v);
  • replacement in pijul/src/remote/http.rs at line 504
    [9.11753][9.11753:11792]()
    format!("{}", rev)
    [9.11753]
    [9.11792]
    rev.to_string()
  • replacement in pijul/src/remote/http.rs at line 506
    [9.11817][9.11817:11851]()
    String::new()
    [9.11817]
    [9.11851]
    0u32.to_string()
  • edit in pijul/src/remote/http.rs at line 511
    [9.3448]
    [9.3448]
    debug!("kv = {:?} {:?}", k, v);
  • replacement in pijul/src/remote/http.rs at line 524
    [9.12254][9.12254:12304]()
    let resp: Identities = res.json().await?;
    [9.12254]
    [9.12304]
    let resp: Option<Identities> = res.json().await?;
  • replacement in pijul/src/remote/http.rs at line 526
    [9.12305][9.12305:12543](),[9.12543][9.266:342](),[9.342][9.12605:12629](),[9.12605][9.12605:12629]()
    std::fs::create_dir_all(&path)?;
    for id in resp.id.iter() {
    path.push(&id.public_key.key);
    debug!("recv identity: {:?} {:?}", id, path);
    let mut id_file = std::fs::File::create(&path)?;
    serde_json::to_writer_pretty(&mut id_file, &id.as_portable())?;
    path.pop();
    [9.12305]
    [9.12629]
    if let Some(resp) = resp {
    std::fs::create_dir_all(&path)?;
    for id in resp.id.iter() {
    path.push(&id.public_key.key);
    debug!("recv identity: {:?} {:?}", id, path);
    let mut id_file = std::fs::File::create(&path)?;
    serde_json::to_writer_pretty(&mut id_file, &id.as_portable())?;
    path.pop();
    }
    Ok(resp.rev)
    } else {
    Ok(0)
  • replacement in pijul/src/remote/http.rs at line 539
    [9.12639][9.12639:12660]()
    Ok(resp.rev)
    [9.12639]
    [9.6503]
    }
    pub async fn prove(
    &mut self,
    key: libpijul::key::SKey,
    ) -> Result<(), anyhow::Error> {
    debug!("prove {:?}", self.url);
    let url = format!("{}/{}", self.url, super::DOT_DIR);
    let q = [("challenge", key.public_key().key)];
    let mut req = self
    .client
    .get(&url)
    .query(&q)
    .header(reqwest::header::USER_AGENT, USER_AGENT);
    for (k, v) in self.headers.iter() {
    debug!("kv = {:?} {:?}", k, v);
    req = req.header(k.as_str(), v.as_str());
    }
    let res = req.send().await?;
    if !res.status().is_success() {
    bail!("HTTP error {:?}", res.status())
    }
    let resp = res.bytes().await?;
    debug!("resp = {:?}", resp);
    let sig = key.sign_raw(&resp)?;
    debug!("sig = {:?}", sig);
    let q = [("prove", &sig)];
    let mut req = self
    .client
    .get(&url)
    .query(&q)
    .header(reqwest::header::USER_AGENT, USER_AGENT);
    for (k, v) in self.headers.iter() {
    debug!("kv = {:?} {:?}", k, v);
    req = req.header(k.as_str(), v.as_str());
    }
    let res = req.send().await?;
    if !res.status().is_success() {
    bail!("HTTP error {:?}", res.status())
    }
    Ok(())
  • edit in pijul/src/main.rs at line 136
    [40.313]
    [3.0]
    /// Authenticates with a HTTP server.
    Client(Client),
  • edit in pijul/src/main.rs at line 272
    [40.683]
    [41.600]
    SubCommand::Client(client) => client.run().await,
  • replacement in pijul/src/identity/create.rs at line 125
    [4.974][4.974:1043]()
    .prove(*NO_CERT_CHECK.get_or_init(|| false))
    [4.974]
    [4.1043]
    .prove(None, *NO_CERT_CHECK.get_or_init(|| false))
  • replacement in pijul/src/identity/create.rs at line 300
    [9.36896][9.36896:36978]()
    pub async fn prove(&self, no_cert_check: bool) -> Result<(), anyhow::Error> {
    [9.36896]
    [9.36978]
    pub async fn prove(
    &self,
    origin: Option<&str>,
    no_cert_check: bool,
    ) -> Result<(), anyhow::Error> {
    let remote = origin.unwrap_or(&self.config.author.origin);
  • replacement in pijul/src/identity/create.rs at line 310
    [4.1638][4.1638:1719]()
    &self.name, &self.config.author.username, &self.config.author.origin
    [4.1638]
    [9.37154]
    &self.name, &self.config.author.username, remote
  • edit in pijul/src/identity/create.rs at line 313
    [9.37167][9.13815:13871]()
    let remote = self.config.author.origin.clone();
  • replacement in pijul/src/identity/create.rs at line 314
    [9.37291][9.37291:37432](),[9.37432][5.746:802](),[5.802][9.37432:37505](),[9.37432][9.37432:37505](),[9.37542][9.37542:37767](),[9.37767][5.803:926](),[5.926][9.37854:38042](),[9.37854][9.37854:38042]()
    use crate::remote::RemoteRepo;
    if let RemoteRepo::Ssh(ssh) = repo
    .remote(
    None,
    Some(&self.config.author.username),
    &remote,
    crate::DEFAULT_CHANNEL,
    no_cert_check,
    false,
    )
    .await?
    {
    ssh
    } else {
    bail!("No such remote: {}", remote)
    }
    } else if let Some(mut ssh) = crate::remote::ssh::ssh_remote(Some(&self.config.author.username), &remote, false) {
    if let Some(c) = ssh.connect(&remote, crate::DEFAULT_CHANNEL).await? {
    c
    } else {
    bail!("No such remote: {}", remote)
    }
    [9.37291]
    [9.38042]
    repo.remote(
    None,
    Some(&self.config.author.username),
    &remote,
    crate::DEFAULT_CHANNEL,
    no_cert_check,
    false,
    )
    .await?
  • replacement in pijul/src/identity/create.rs at line 324
    [9.38059][9.38059:38107]()
    bail!("No such remote: {}", remote)
    [9.38059]
    [9.38107]
    crate::remote::unknown_remote(
    None,
    Some(&self.config.author.username),
    &remote,
    crate::DEFAULT_CHANNEL,
    no_cert_check,
    false,
    )
    .await?
  • replacement in pijul/src/config.rs at line 206
    [9.4809][9.4809:4848]()
    Ok(String::from_utf8(out.stdout)?)
    [9.4809]
    [9.4848]
    Ok(String::from_utf8(out.stdout)?.trim().to_string())
  • edit in pijul/src/commands/mod.rs at line 65
    [9.2548][9.18549:18577](),[9.91][9.18549:18577]()
    // #[cfg(debug_assertions)]
  • edit in pijul/src/commands/mod.rs at line 66
    [9.134289][9.18578:18606]()
    // #[cfg(debug_assertions)]
  • edit in pijul/src/commands/mod.rs at line 68
    [9.134333]
    [42.138]
    mod client;
    pub use client::*;
  • edit in pijul/src/commands/identity.rs at line 167
    [9.46469]
    [9.46469]
    /// Set the target server
    server: Option<String>,
  • edit in pijul/src/commands/identity.rs at line 268
    [9.49543]
    [9.49543]
    server,
  • replacement in pijul/src/commands/identity.rs at line 272
    [9.49687][9.49687:49734]()
    .prove(self.no_cert_check)
    [9.49687]
    [9.49734]
    .prove(server.as_deref(), self.no_cert_check)
  • file addition: client.rs (----------)
    [9.93386]
    use clap::Parser;
    use std::convert::Infallible;
    use std::net::SocketAddr;
    use hyper::{Body, Request, Response, Server};
    use hyper::service::{make_service_fn, service_fn};
    use tokio::sync::mpsc::channel;
    use tokio::select;
    use crate::config::global_config_dir;
    #[derive(Parser, Debug)]
    pub struct Client {
    /// Url to authenticate to.
    #[clap(value_name = "URL")]
    url: String,
    }
    impl Client {
    pub async fn run(self) -> Result<(), anyhow::Error> {
    let url = url::Url::parse(&self.url)?;
    let mut cache_path = None;
    if let Some(mut cached) = global_config_dir() {
    cached.push("cache");
    if let Some(host) = url.host_str() {
    std::fs::create_dir_all(&cached)?;
    cached.push(host);
    if let Ok(token) = std::fs::read_to_string(&cached) {
    println!("Bearer {}", token);
    return Ok(())
    } else {
    cache_path = Some(cached);
    }
    }
    }
    let (tx, mut rx) = channel::<String>(1);
    let make_service = make_service_fn(|_conn| {
    let tx = tx.clone();
    async move {
    let handle = move |req: Request<_>| {
    let qq: Option<String> = if let Some(q) = req.uri().query() {
    let eq = "token=";
    if q.starts_with(eq) {
    Some(q.split_at(eq.len()).1.to_string())
    } else {
    None
    }
    } else {
    None
    };
    let tx = tx.clone();
    async move {
    if let Some(qq) = qq {
    tx.send(qq).await.unwrap();
    let resp = Response::builder()
    .header("Content-Type", "text/html")
    .body(Body::from(include_str!("client.html")))
    .unwrap();
    Ok::<_, Infallible>(resp)
    } else {
    Ok::<_, Infallible>(
    Response::builder()
    .status(404)
    .body("Not found".into())
    .unwrap()
    )
    }
    }
    };
    Ok::<_, Infallible>(service_fn(handle))
    }
    });
    let mut port = 3000;
    loop {
    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    if let Ok(server) = Server::try_bind(&addr) {
    let mut url = url::Url::parse(&self.url)?;
    url.query_pairs_mut().append_pair("port", &port.to_string());
    open::that(&url.to_string())?;
    let server = server.serve(make_service);
    select!{
    x = server => {
    if let Err(e) = x {
    eprintln!("server error: {}", e);
    }
    break
    }
    x = rx.recv() => {
    if let Some(x) = x {
    if let Some(cache_path) = cache_path {
    std::fs::write(&cache_path, &x)?;
    }
    println!("Bearer {}", x);
    }
    break
    }
    }
    }
    if port < u16::MAX {
    port += 1
    } else {
    break
    }
    }
    Ok(())
    }
    }
  • edit in pijul/Cargo.toml at line 77
    [9.1679]
    [9.25010]
    hyper = { version = "0.14", features = [ "server" ] }
  • replacement in pijul/Cargo.toml at line 98
    [2.17][9.2288:2300](),[9.301][9.2288:2300](),[9.472][9.2288:2300]()
    url = "2.2"
    [2.17]
    [9.4195]
    url = "2.3"
  • edit in pijul/Cargo.toml at line 111
    [8.279]
    [9.309]
    open = "3"
    bs58 = "0.4"