public inbox for pbs-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates
@ 2021-05-11 13:53 Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 1/7] proxy: factor out accept_connection Wolfgang Bumiller
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

This adds the ability to tell a running proxy to just reload the TLS
cert certificates via the command-socket.

Starts off with some cleanup/refactoring to get rid of all that heavy
indentation...

Wolfgang Bumiller (7):
  proxy: factor out accept_connection
  proxy: "continue on error" for the accept call, too
  proxy: Arc usage cleanup
  proxy: factor out tls acceptor creation
  proxy: implement 'reload-certificate' command
  refactor send_command
  hot-reload proxy certificate when updating via the API

 src/api2/node/certificates.rs   |  26 ++--
 src/bin/proxmox-backup-proxy.rs | 220 ++++++++++++++++++++------------
 src/config.rs                   |  17 +--
 src/server.rs                   |   9 ++
 src/server/command_socket.rs    |  71 ++++++-----
 src/server/worker_task.rs       |   4 +-
 6 files changed, 204 insertions(+), 143 deletions(-)

-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 1/7] proxy: factor out accept_connection
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 2/7] proxy: "continue on error" for the accept call, too Wolfgang Bumiller
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

no functional changes, moved code and named the channel's
type for more readability

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
diff -w for a quicker view:
    diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
    index 31dc8332..27d1cbeb 100644
    --- a/src/bin/proxmox-backup-proxy.rs
    +++ b/src/bin/proxmox-backup-proxy.rs
    @@ -170,19 +170,31 @@ async fn run() -> Result<(), Error> {
         Ok(())
     }

    +type ClientStreamResult =
    +    Result<std::pin::Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>, Error>;
    +const MAX_PENDING_ACCEPTS: usize = 1024;
    +
     fn accept_connections(
         listener: tokio::net::TcpListener,
         acceptor: Arc<openssl::ssl::SslAcceptor>,
         debug: bool,
    -) -> tokio::sync::mpsc::Receiver<Result<std::pin::Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>, Error>> {
    -
    -    const MAX_PENDING_ACCEPTS: usize = 1024;
    +) -> tokio::sync::mpsc::Receiver<ClientStreamResult> {

         let (sender, receiver) = tokio::sync::mpsc::channel(MAX_PENDING_ACCEPTS);

    +    tokio::spawn(accept_connection(listener, acceptor, debug, sender));
    +
    +    receiver
    +}
    +
    +async fn accept_connection(
    +    listener: tokio::net::TcpListener,
    +    acceptor: Arc<openssl::ssl::SslAcceptor>,
    +    debug: bool,
    +    sender: tokio::sync::mpsc::Sender<ClientStreamResult>,
    +) {
         let accept_counter = Arc::new(());

    -    tokio::spawn(async move {
         loop {
             match listener.accept().await {
                 Err(err) => {
    @@ -246,9 +258,6 @@ fn accept_connections(
                 }
             }
         }
    -    });
    -
    -    receiver
     }


 src/bin/proxmox-backup-proxy.rs | 127 +++++++++++++++++---------------
 1 file changed, 68 insertions(+), 59 deletions(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index 31dc8332..27d1cbeb 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -170,85 +170,94 @@ async fn run() -> Result<(), Error> {
     Ok(())
 }
 
+type ClientStreamResult =
+    Result<std::pin::Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>, Error>;
+const MAX_PENDING_ACCEPTS: usize = 1024;
+
 fn accept_connections(
     listener: tokio::net::TcpListener,
     acceptor: Arc<openssl::ssl::SslAcceptor>,
     debug: bool,
-) -> tokio::sync::mpsc::Receiver<Result<std::pin::Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>, Error>> {
-
-    const MAX_PENDING_ACCEPTS: usize = 1024;
+) -> tokio::sync::mpsc::Receiver<ClientStreamResult> {
 
     let (sender, receiver) = tokio::sync::mpsc::channel(MAX_PENDING_ACCEPTS);
 
+    tokio::spawn(accept_connection(listener, acceptor, debug, sender));
+
+    receiver
+}
+
+async fn accept_connection(
+    listener: tokio::net::TcpListener,
+    acceptor: Arc<openssl::ssl::SslAcceptor>,
+    debug: bool,
+    sender: tokio::sync::mpsc::Sender<ClientStreamResult>,
+) {
     let accept_counter = Arc::new(());
 
-    tokio::spawn(async move {
-        loop {
-            match listener.accept().await {
-                Err(err) => {
-                    eprintln!("error accepting tcp connection: {}", err);
-                }
-                Ok((sock, _addr)) =>  {
-                    sock.set_nodelay(true).unwrap();
-                    let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
-                    let acceptor = Arc::clone(&acceptor);
-
-                    let ssl = match openssl::ssl::Ssl::new(acceptor.context()) {
-                        Ok(ssl) => ssl,
-                        Err(err) => {
-                            eprintln!("failed to create Ssl object from Acceptor context - {}", err);
-                            continue;
-                        },
-                    };
-                    let stream = match tokio_openssl::SslStream::new(ssl, sock) {
-                        Ok(stream) => stream,
-                        Err(err) => {
-                            eprintln!("failed to create SslStream using ssl and connection socket - {}", err);
-                            continue;
-                        },
-                    };
-
-                    let mut stream = Box::pin(stream);
-                    let sender = sender.clone();
-
-                    if Arc::strong_count(&accept_counter) > MAX_PENDING_ACCEPTS {
-                        eprintln!("connection rejected - to many open connections");
+    loop {
+        match listener.accept().await {
+            Err(err) => {
+                eprintln!("error accepting tcp connection: {}", err);
+            }
+            Ok((sock, _addr)) =>  {
+                sock.set_nodelay(true).unwrap();
+                let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
+                let acceptor = Arc::clone(&acceptor);
+
+                let ssl = match openssl::ssl::Ssl::new(acceptor.context()) {
+                    Ok(ssl) => ssl,
+                    Err(err) => {
+                        eprintln!("failed to create Ssl object from Acceptor context - {}", err);
                         continue;
-                    }
+                    },
+                };
+                let stream = match tokio_openssl::SslStream::new(ssl, sock) {
+                    Ok(stream) => stream,
+                    Err(err) => {
+                        eprintln!("failed to create SslStream using ssl and connection socket - {}", err);
+                        continue;
+                    },
+                };
+
+                let mut stream = Box::pin(stream);
+                let sender = sender.clone();
+
+                if Arc::strong_count(&accept_counter) > MAX_PENDING_ACCEPTS {
+                    eprintln!("connection rejected - to many open connections");
+                    continue;
+                }
 
-                    let accept_counter = accept_counter.clone();
-                    tokio::spawn(async move {
-                        let accept_future = tokio::time::timeout(
-                            Duration::new(10, 0), stream.as_mut().accept());
+                let accept_counter = accept_counter.clone();
+                tokio::spawn(async move {
+                    let accept_future = tokio::time::timeout(
+                        Duration::new(10, 0), stream.as_mut().accept());
 
-                        let result = accept_future.await;
+                    let result = accept_future.await;
 
-                        match result {
-                            Ok(Ok(())) => {
-                                if sender.send(Ok(stream)).await.is_err() && debug {
-                                    eprintln!("detect closed connection channel");
-                                }
+                    match result {
+                        Ok(Ok(())) => {
+                            if sender.send(Ok(stream)).await.is_err() && debug {
+                                eprintln!("detect closed connection channel");
                             }
-                            Ok(Err(err)) => {
-                                if debug {
-                                    eprintln!("https handshake failed - {}", err);
-                                }
+                        }
+                        Ok(Err(err)) => {
+                            if debug {
+                                eprintln!("https handshake failed - {}", err);
                             }
-                            Err(_) => {
-                                if debug {
-                                    eprintln!("https handshake timeout");
-                                }
+                        }
+                        Err(_) => {
+                            if debug {
+                                eprintln!("https handshake timeout");
                             }
                         }
+                    }
 
-                        drop(accept_counter); // decrease reference count
-                    });
-                }
+                    drop(accept_counter); // decrease reference count
+                });
             }
         }
-    });
-
-    receiver
+    }
 }
 
 fn start_stat_generator() {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 2/7] proxy: "continue on error" for the accept call, too
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 1/7] proxy: factor out accept_connection Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 3/7] proxy: Arc usage cleanup Wolfgang Bumiller
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

as this gets rid of 2 levels of indentation

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
diff -w for a quicker view:
    diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
    index 27d1cbeb..b28ab75b 100644
    --- a/src/bin/proxmox-backup-proxy.rs
    +++ b/src/bin/proxmox-backup-proxy.rs
    @@ -196,11 +196,14 @@ async fn accept_connection(
         let accept_counter = Arc::new(());

         loop {
    -        match listener.accept().await {
    +        let (sock, _addr) = match listener.accept().await {
    +            Ok(conn) => conn,
                 Err(err) => {
                     eprintln!("error accepting tcp connection: {}", err);
    +                continue;
                 }
    -            Ok((sock, _addr)) =>  {
    +        };
    +
             sock.set_nodelay(true).unwrap();
             let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
             let acceptor = Arc::clone(&acceptor);
    @@ -257,8 +260,6 @@ async fn accept_connection(
             });
         }
     }
    -    }
    -}

     fn start_stat_generator() {
         let abort_future = server::shutdown_future();


 src/bin/proxmox-backup-proxy.rs | 107 ++++++++++++++++----------------
 1 file changed, 54 insertions(+), 53 deletions(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index 27d1cbeb..b28ab75b 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -196,67 +196,68 @@ async fn accept_connection(
     let accept_counter = Arc::new(());
 
     loop {
-        match listener.accept().await {
+        let (sock, _addr) = match listener.accept().await {
+            Ok(conn) => conn,
             Err(err) => {
                 eprintln!("error accepting tcp connection: {}", err);
+                continue;
             }
-            Ok((sock, _addr)) =>  {
-                sock.set_nodelay(true).unwrap();
-                let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
-                let acceptor = Arc::clone(&acceptor);
-
-                let ssl = match openssl::ssl::Ssl::new(acceptor.context()) {
-                    Ok(ssl) => ssl,
-                    Err(err) => {
-                        eprintln!("failed to create Ssl object from Acceptor context - {}", err);
-                        continue;
-                    },
-                };
-                let stream = match tokio_openssl::SslStream::new(ssl, sock) {
-                    Ok(stream) => stream,
-                    Err(err) => {
-                        eprintln!("failed to create SslStream using ssl and connection socket - {}", err);
-                        continue;
-                    },
-                };
-
-                let mut stream = Box::pin(stream);
-                let sender = sender.clone();
-
-                if Arc::strong_count(&accept_counter) > MAX_PENDING_ACCEPTS {
-                    eprintln!("connection rejected - to many open connections");
-                    continue;
-                }
+        };
 
-                let accept_counter = accept_counter.clone();
-                tokio::spawn(async move {
-                    let accept_future = tokio::time::timeout(
-                        Duration::new(10, 0), stream.as_mut().accept());
+        sock.set_nodelay(true).unwrap();
+        let _ = set_tcp_keepalive(sock.as_raw_fd(), PROXMOX_BACKUP_TCP_KEEPALIVE_TIME);
+        let acceptor = Arc::clone(&acceptor);
 
-                    let result = accept_future.await;
+        let ssl = match openssl::ssl::Ssl::new(acceptor.context()) {
+            Ok(ssl) => ssl,
+            Err(err) => {
+                eprintln!("failed to create Ssl object from Acceptor context - {}", err);
+                continue;
+            },
+        };
+        let stream = match tokio_openssl::SslStream::new(ssl, sock) {
+            Ok(stream) => stream,
+            Err(err) => {
+                eprintln!("failed to create SslStream using ssl and connection socket - {}", err);
+                continue;
+            },
+        };
 
-                    match result {
-                        Ok(Ok(())) => {
-                            if sender.send(Ok(stream)).await.is_err() && debug {
-                                eprintln!("detect closed connection channel");
-                            }
-                        }
-                        Ok(Err(err)) => {
-                            if debug {
-                                eprintln!("https handshake failed - {}", err);
-                            }
-                        }
-                        Err(_) => {
-                            if debug {
-                                eprintln!("https handshake timeout");
-                            }
-                        }
-                    }
+        let mut stream = Box::pin(stream);
+        let sender = sender.clone();
 
-                    drop(accept_counter); // decrease reference count
-                });
-            }
+        if Arc::strong_count(&accept_counter) > MAX_PENDING_ACCEPTS {
+            eprintln!("connection rejected - to many open connections");
+            continue;
         }
+
+        let accept_counter = accept_counter.clone();
+        tokio::spawn(async move {
+            let accept_future = tokio::time::timeout(
+                Duration::new(10, 0), stream.as_mut().accept());
+
+            let result = accept_future.await;
+
+            match result {
+                Ok(Ok(())) => {
+                    if sender.send(Ok(stream)).await.is_err() && debug {
+                        eprintln!("detect closed connection channel");
+                    }
+                }
+                Ok(Err(err)) => {
+                    if debug {
+                        eprintln!("https handshake failed - {}", err);
+                    }
+                }
+                Err(_) => {
+                    if debug {
+                        eprintln!("https handshake timeout");
+                    }
+                }
+            }
+
+            drop(accept_counter); // decrease reference count
+        });
     }
 }
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 3/7] proxy: Arc usage cleanup
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 1/7] proxy: factor out accept_connection Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 2/7] proxy: "continue on error" for the accept call, too Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 4/7] proxy: factor out tls acceptor creation Wolfgang Bumiller
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/bin/proxmox-backup-proxy.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index b28ab75b..0b4b5586 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -231,7 +231,7 @@ async fn accept_connection(
             continue;
         }
 
-        let accept_counter = accept_counter.clone();
+        let accept_counter = Arc::clone(&accept_counter);
         tokio::spawn(async move {
             let accept_future = tokio::time::timeout(
                 Duration::new(10, 0), stream.as_mut().accept());
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 4/7] proxy: factor out tls acceptor creation
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
                   ` (2 preceding siblings ...)
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 3/7] proxy: Arc usage cleanup Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 5/7] proxy: implement 'reload-certificate' command Wolfgang Bumiller
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/bin/proxmox-backup-proxy.rs | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index 0b4b5586..c05bcd20 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -113,15 +113,11 @@ async fn run() -> Result<(), Error> {
     let rest_server = RestServer::new(config);
 
     //openssl req -x509 -newkey rsa:4096 -keyout /etc/proxmox-backup/proxy.key -out /etc/proxmox-backup/proxy.pem -nodes
-    let key_path = configdir!("/proxy.key");
-    let cert_path = configdir!("/proxy.pem");
 
-    let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
-    acceptor.set_private_key_file(key_path, SslFiletype::PEM)
-        .map_err(|err| format_err!("unable to read proxy key {} - {}", key_path, err))?;
-    acceptor.set_certificate_chain_file(cert_path)
-        .map_err(|err| format_err!("unable to read proxy cert {} - {}", cert_path, err))?;
-    acceptor.check_private_key().unwrap();
+    // we build the initial acceptor here as we cannot start if this fails - certificate reloads
+    // will be handled inside the accept loop and simply log an error if we cannot load the new
+    // certificate!
+    let acceptor = make_tls_acceptor()?;
 
     let acceptor = Arc::new(acceptor.build());
 
@@ -170,6 +166,20 @@ async fn run() -> Result<(), Error> {
     Ok(())
 }
 
+fn make_tls_acceptor() -> Result<Arc<SslAcceptor>, Error> {
+    let key_path = configdir!("/proxy.key");
+    let cert_path = configdir!("/proxy.pem");
+
+    let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
+    acceptor.set_private_key_file(key_path, SslFiletype::PEM)
+        .map_err(|err| format_err!("unable to read proxy key {} - {}", key_path, err))?;
+    acceptor.set_certificate_chain_file(cert_path)
+        .map_err(|err| format_err!("unable to read proxy cert {} - {}", cert_path, err))?;
+    acceptor.check_private_key().unwrap();
+
+    Ok(Arc::new(acceptor.build()))
+}
+
 type ClientStreamResult =
     Result<std::pin::Pin<Box<tokio_openssl::SslStream<tokio::net::TcpStream>>>, Error>;
 const MAX_PENDING_ACCEPTS: usize = 1024;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 5/7] proxy: implement 'reload-certificate' command
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
                   ` (3 preceding siblings ...)
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 4/7] proxy: factor out tls acceptor creation Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 6/7] refactor send_command Wolfgang Bumiller
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

to be used via the command socket

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/bin/proxmox-backup-proxy.rs | 62 ++++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 9 deletions(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index c05bcd20..793ba67d 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -1,5 +1,6 @@
 use std::sync::Arc;
 use std::path::{Path, PathBuf};
+use std::pin::Pin;
 use std::os::unix::io::AsRawFd;
 
 use anyhow::{bail, format_err, Error};
@@ -7,6 +8,7 @@ use futures::*;
 
 use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
 use tokio_stream::wrappers::ReceiverStream;
+use serde_json::Value;
 
 use proxmox::try_block;
 use proxmox::api::RpcEnvironmentType;
@@ -119,13 +121,24 @@ async fn run() -> Result<(), Error> {
     // certificate!
     let acceptor = make_tls_acceptor()?;
 
-    let acceptor = Arc::new(acceptor.build());
+    // to renew the acceptor we just let a command-socket handler trigger a Notify:
+    let notify_tls_cert_reload = Arc::new(tokio::sync::Notify::new());
+    commando_sock.register_command(
+        "reload-certificate".to_string(),
+        {
+            let notify_tls_cert_reload = Arc::clone(&notify_tls_cert_reload);
+            move |_value| -> Result<_, Error> {
+                notify_tls_cert_reload.notify_one();
+                Ok(Value::Null)
+            }
+        },
+    )?;
 
     let server = daemon::create_daemon(
         ([0,0,0,0,0,0,0,0], 8007).into(),
-        |listener, ready| {
+        move |listener, ready| {
 
-            let connections = accept_connections(listener, acceptor, debug);
+            let connections = accept_connections(listener, acceptor, debug, notify_tls_cert_reload);
             let connections = hyper::server::accept::from_stream(ReceiverStream::new(connections));
 
             Ok(ready
@@ -188,30 +201,61 @@ fn accept_connections(
     listener: tokio::net::TcpListener,
     acceptor: Arc<openssl::ssl::SslAcceptor>,
     debug: bool,
+    notify_tls_cert_reload: Arc<tokio::sync::Notify>,
 ) -> tokio::sync::mpsc::Receiver<ClientStreamResult> {
 
     let (sender, receiver) = tokio::sync::mpsc::channel(MAX_PENDING_ACCEPTS);
 
-    tokio::spawn(accept_connection(listener, acceptor, debug, sender));
+    tokio::spawn(accept_connection(listener, acceptor, debug, sender, notify_tls_cert_reload));
 
     receiver
 }
 
 async fn accept_connection(
     listener: tokio::net::TcpListener,
-    acceptor: Arc<openssl::ssl::SslAcceptor>,
+    mut acceptor: Arc<openssl::ssl::SslAcceptor>,
     debug: bool,
     sender: tokio::sync::mpsc::Sender<ClientStreamResult>,
+    notify_tls_cert_reload: Arc<tokio::sync::Notify>,
 ) {
     let accept_counter = Arc::new(());
 
+    // Note that these must not be moved out/modified directly, they get pinned in the loop and
+    // "rearmed" after waking up:
+    let mut reload_tls = notify_tls_cert_reload.notified();
+    let mut accept = listener.accept();
+
     loop {
-        let (sock, _addr) = match listener.accept().await {
-            Ok(conn) => conn,
-            Err(err) => {
-                eprintln!("error accepting tcp connection: {}", err);
+        let sock;
+
+        // normally we'd use `tokio::pin!()` but we need this to happen outside the loop and we
+        // need to be able to "rearm" the futures:
+        let reload_tls_pin = unsafe { Pin::new_unchecked(&mut reload_tls) };
+        let accept_pin = unsafe { Pin::new_unchecked(&mut accept) };
+        tokio::select! {
+            _ = reload_tls_pin => {
+                // rearm the notification:
+                reload_tls = notify_tls_cert_reload.notified();
+
+                log::info!("reloading certificate");
+                match make_tls_acceptor() {
+                    Err(err) => eprintln!("error reloading certificate: {}", err),
+                    Ok(new_acceptor) => acceptor = new_acceptor,
+                }
                 continue;
             }
+            res = accept_pin => match res {
+                Err(err) => {
+                    eprintln!("error accepting tcp connection: {}", err);
+                    continue;
+                }
+                Ok((new_sock, _addr)) => {
+                    // rearm the accept future:
+                    accept = listener.accept();
+
+                    sock = new_sock;
+                }
+            }
         };
 
         sock.set_nodelay(true).unwrap();
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 6/7] refactor send_command
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
                   ` (4 preceding siblings ...)
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 5/7] proxy: implement 'reload-certificate' command Wolfgang Bumiller
@ 2021-05-11 13:53 ` Wolfgang Bumiller
  2021-05-11 13:54 ` [pbs-devel] [PATCH backup 7/7] hot-reload proxy certificate when updating via the API Wolfgang Bumiller
  2021-05-11 16:11 ` [pbs-devel] applied-series: [PATCH backup 0/7] hot-reload proxy certificates Thomas Lamprecht
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:53 UTC (permalink / raw)
  To: pbs-devel

- refactor the combinators,
- make it take a `&T: Serialize` instead of a Value, and
  allow sending the raw string via `send_raw_command`.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/bin/proxmox-backup-proxy.rs |  8 +---
 src/server/command_socket.rs    | 71 ++++++++++++++++++---------------
 src/server/worker_task.rs       |  4 +-
 3 files changed, 42 insertions(+), 41 deletions(-)

diff --git a/src/bin/proxmox-backup-proxy.rs b/src/bin/proxmox-backup-proxy.rs
index 793ba67d..fc773459 100644
--- a/src/bin/proxmox-backup-proxy.rs
+++ b/src/bin/proxmox-backup-proxy.rs
@@ -750,15 +750,11 @@ async fn command_reopen_logfiles() -> Result<(), Error> {
     // only care about the most recent daemon instance for each, proxy & api, as other older ones
     // should not respond to new requests anyway, but only finish their current one and then exit.
     let sock = server::our_ctrl_sock();
-    let f1 = server::send_command(sock, serde_json::json!({
-        "command": "api-access-log-reopen",
-    }));
+    let f1 = server::send_command(sock, "{\"command\":\"api-access-log-reopen\"}\n");
 
     let pid = server::read_pid(buildcfg::PROXMOX_BACKUP_API_PID_FN)?;
     let sock = server::ctrl_sock_from_pid(pid);
-    let f2 = server::send_command(sock, serde_json::json!({
-        "command": "api-access-log-reopen",
-    }));
+    let f2 = server::send_command(sock, "{\"command\":\"api-access-log-reopen\"}\n");
 
     match futures::join!(f1, f2) {
         (Err(e1), Err(e2)) => Err(format_err!("reopen commands failed, proxy: {}; api: {}", e1, e2)),
diff --git a/src/server/command_socket.rs b/src/server/command_socket.rs
index 89c77585..af41dd16 100644
--- a/src/server/command_socket.rs
+++ b/src/server/command_socket.rs
@@ -2,11 +2,12 @@ use anyhow::{bail, format_err, Error};
 
 use std::collections::HashMap;
 use std::os::unix::io::AsRawFd;
-use std::path::PathBuf;
+use std::path::{PathBuf, Path};
 use std::sync::Arc;
 
 use futures::*;
 use tokio::net::UnixListener;
+use serde::Serialize;
 use serde_json::Value;
 use nix::sys::socket;
 
@@ -102,43 +103,47 @@ where
 }
 
 
-pub async fn send_command<P>(
-    path: P,
-    params: Value
-) -> Result<Value, Error>
-    where P: Into<PathBuf>,
+pub async fn send_command<P, T>(path: P, params: &T) -> Result<Value, Error>
+where
+    P: AsRef<Path>,
+    T: ?Sized + Serialize,
 {
-    let path: PathBuf = path.into();
+    let mut command_string = serde_json::to_string(params)?;
+    command_string.push('\n');
+    send_raw_command(path.as_ref(), &command_string).await
+}
 
-    tokio::net::UnixStream::connect(path)
-        .map_err(move |err| format_err!("control socket connect failed - {}", err))
-        .and_then(move |mut conn| {
+pub async fn send_raw_command<P>(path: P, command_string: &str) -> Result<Value, Error>
+where
+    P: AsRef<Path>,
+{
+    use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
 
-            let mut command_string = params.to_string();
-            command_string.push('\n');
+    let mut conn = tokio::net::UnixStream::connect(path)
+        .map_err(move |err| format_err!("control socket connect failed - {}", err))
+        .await?;
 
-            async move {
-                use tokio::io::{AsyncBufReadExt, AsyncWriteExt};
+    conn.write_all(command_string.as_bytes()).await?;
+    if !command_string.as_bytes().ends_with(b"\n") {
+        conn.write_all(b"\n").await?;
+    }
 
-                conn.write_all(command_string.as_bytes()).await?;
-                AsyncWriteExt::shutdown(&mut conn).await?;
-                let mut rx = tokio::io::BufReader::new(conn);
-                let mut data = String::new();
-                if rx.read_line(&mut data).await? == 0 {
-                    bail!("no response");
-                }
-                if let Some(res) = data.strip_prefix("OK: ") {
-                    match res.parse::<Value>() {
-                        Ok(v) => Ok(v),
-                        Err(err) => bail!("unable to parse json response - {}", err),
-                    }
-                } else if let Some(err) = data.strip_prefix("ERROR: ") {
-                    bail!("{}", err);
-                } else {
-                    bail!("unable to parse response: {}", data);
-                }
-            }
-        }).await
+    AsyncWriteExt::shutdown(&mut conn).await?;
+    let mut rx = tokio::io::BufReader::new(conn);
+    let mut data = String::new();
+    if rx.read_line(&mut data).await? == 0 {
+        bail!("no response");
+    }
+    if let Some(res) = data.strip_prefix("OK: ") {
+        match res.parse::<Value>() {
+            Ok(v) => Ok(v),
+            Err(err) => bail!("unable to parse json response - {}", err),
+        }
+    } else if let Some(err) = data.strip_prefix("ERROR: ") {
+        bail!("{}", err);
+    } else {
+        bail!("unable to parse response: {}", data);
+    }
 }
 
 /// A callback for a specific commando socket.
diff --git a/src/server/worker_task.rs b/src/server/worker_task.rs
index 6c5456c9..84019fef 100644
--- a/src/server/worker_task.rs
+++ b/src/server/worker_task.rs
@@ -59,7 +59,7 @@ pub async fn worker_is_active(upid: &UPID) -> Result<bool, Error> {
             "upid": upid.to_string(),
         },
     });
-    let status = super::send_command(sock, cmd).await?;
+    let status = super::send_command(sock, &cmd).await?;
 
     if let Some(active) = status.as_bool() {
         Ok(active)
@@ -133,7 +133,7 @@ pub async fn abort_worker(upid: UPID) -> Result<(), Error> {
             "upid": upid.to_string(),
         },
     });
-    super::send_command(sock, cmd).map_ok(|_| ()).await
+    super::send_command(sock, &cmd).map_ok(|_| ()).await
 }
 
 fn parse_worker_status_line(line: &str) -> Result<(String, UPID, Option<TaskState>), Error> {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] [PATCH backup 7/7] hot-reload proxy certificate when updating via the API
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
                   ` (5 preceding siblings ...)
  2021-05-11 13:53 ` [pbs-devel] [PATCH backup 6/7] refactor send_command Wolfgang Bumiller
@ 2021-05-11 13:54 ` Wolfgang Bumiller
  2021-05-11 16:11 ` [pbs-devel] applied-series: [PATCH backup 0/7] hot-reload proxy certificates Thomas Lamprecht
  7 siblings, 0 replies; 9+ messages in thread
From: Wolfgang Bumiller @ 2021-05-11 13:54 UTC (permalink / raw)
  To: pbs-devel

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
---
 src/api2/node/certificates.rs | 26 +++++++++++++-------------
 src/config.rs                 | 17 ++---------------
 src/server.rs                 |  9 +++++++++
 3 files changed, 24 insertions(+), 28 deletions(-)

diff --git a/src/api2/node/certificates.rs b/src/api2/node/certificates.rs
index e6ad59b3..79df5d0f 100644
--- a/src/api2/node/certificates.rs
+++ b/src/api2/node/certificates.rs
@@ -175,12 +175,13 @@ pub fn get_info() -> Result<Vec<CertificateInfo>, Error> {
             node: { schema: NODE_SCHEMA },
             certificates: { description: "PEM encoded certificate (chain)." },
             key: { description: "PEM encoded private key." },
+            // FIXME: widget-toolkit should have an option to disable using these 2 parameters...
             restart: {
-                description: "Restart proxmox-backup-proxy",
+                description: "UI compatibility parameter, ignored",
+                type: Boolean,
                 optional: true,
                 default: false,
             },
-            // FIXME: widget-toolkit should have an option to disable using this parameter...
             force: {
                 description: "Force replacement of existing files.",
                 type: Boolean,
@@ -200,10 +201,9 @@ pub fn get_info() -> Result<Vec<CertificateInfo>, Error> {
     protected: true,
 )]
 /// Upload a custom certificate.
-pub fn upload_custom_certificate(
+pub async fn upload_custom_certificate(
     certificates: String,
     key: String,
-    restart: bool,
 ) -> Result<Vec<CertificateInfo>, Error> {
     let certificates = X509::stack_from_pem(certificates.as_bytes())
         .map_err(|err| format_err!("failed to decode certificate chain: {}", err))?;
@@ -223,7 +223,8 @@ pub fn upload_custom_certificate(
 
     let key = key.private_key_to_pem_pkcs8()?;
 
-    crate::config::set_proxy_certificate(&certificates, &key, restart)?;
+    crate::config::set_proxy_certificate(&certificates, &key)?;
+    crate::server::reload_proxy_certificate().await?;
 
     get_info()
 }
@@ -233,7 +234,8 @@ pub fn upload_custom_certificate(
         properties: {
             node: { schema: NODE_SCHEMA },
             restart: {
-                description: "Restart proxmox-backup-proxy",
+                description: "UI compatibility parameter, ignored",
+                type: Boolean,
                 optional: true,
                 default: false,
             },
@@ -245,7 +247,7 @@ pub fn upload_custom_certificate(
     protected: true,
 )]
 /// Delete the current certificate and regenerate a self signed one.
-pub fn delete_custom_certificate(restart: bool) -> Result<(), Error> {
+pub async fn delete_custom_certificate() -> Result<(), Error> {
     let cert_path = configdir!("/proxy.pem");
     // Here we fail since if this fails nothing else breaks anyway
     std::fs::remove_file(&cert_path)
@@ -263,10 +265,7 @@ pub fn delete_custom_certificate(restart: bool) -> Result<(), Error> {
     }
 
     crate::config::update_self_signed_cert(true)?;
-
-    if restart {
-        crate::config::reload_proxy()?;
-    }
+    crate::server::reload_proxy_certificate().await?;
 
     Ok(())
 }
@@ -535,7 +534,8 @@ fn spawn_certificate_worker(
 
     WorkerTask::spawn(name, None, auth_id, true, move |worker| async move {
         if let Some(cert) = order_certificate(worker, &node_config).await? {
-            crate::config::set_proxy_certificate(&cert.certificate, &cert.private_key_pem, true)?;
+            crate::config::set_proxy_certificate(&cert.certificate, &cert.private_key_pem)?;
+            crate::server::reload_proxy_certificate().await?;
         }
         Ok(())
     })
@@ -572,7 +572,7 @@ pub fn revoke_acme_cert(rpcenv: &mut dyn RpcEnvironment) -> Result<String, Error
             worker.log("Revoking old certificate");
             acme.revoke_certificate(cert_pem.as_bytes(), None).await?;
             worker.log("Deleting certificate and regenerating a self-signed one");
-            delete_custom_certificate(true)?;
+            delete_custom_certificate().await?;
             Ok(())
         },
     )
diff --git a/src/config.rs b/src/config.rs
index 22c293c9..b9cd6281 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -187,16 +187,12 @@ pub fn update_self_signed_cert(force: bool) -> Result<(), Error> {
     let x509 = x509.build();
     let cert_pem = x509.to_pem()?;
 
-    set_proxy_certificate(&cert_pem, &priv_pem, false)?;
+    set_proxy_certificate(&cert_pem, &priv_pem)?;
 
     Ok(())
 }
 
-pub(crate) fn set_proxy_certificate(
-    cert_pem: &[u8],
-    key_pem: &[u8],
-    reload: bool,
-) -> Result<(), Error> {
+pub(crate) fn set_proxy_certificate(cert_pem: &[u8], key_pem: &[u8]) -> Result<(), Error> {
     let backup_user = crate::backup::backup_user()?;
     let options = CreateOptions::new()
         .perm(Mode::from_bits_truncate(0o0640))
@@ -211,14 +207,5 @@ pub(crate) fn set_proxy_certificate(
     replace_file(&cert_path, &cert_pem, options)
         .map_err(|err| format_err!("error writing certificate file - {}", err))?;
 
-    if reload {
-        reload_proxy()?;
-    }
-
     Ok(())
 }
-
-pub(crate) fn reload_proxy() -> Result<(), Error> {
-    crate::tools::systemd::reload_unit("proxmox-backup-proxy")
-        .map_err(|err| format_err!("error signaling reload to pbs proxy: {}", err))
-}
diff --git a/src/server.rs b/src/server.rs
index b6a37b92..ba25617d 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -7,6 +7,7 @@
 use anyhow::{format_err, Error};
 use lazy_static::lazy_static;
 use nix::unistd::Pid;
+use serde_json::Value;
 
 use proxmox::sys::linux::procfs::PidStat;
 
@@ -91,3 +92,11 @@ pub use report::*;
 pub mod ticket;
 
 pub mod auth;
+
+pub(crate) async fn reload_proxy_certificate() -> Result<(), Error> {
+    let proxy_pid = crate::server::read_pid(buildcfg::PROXMOX_BACKUP_PROXY_PID_FN)?;
+    let sock = crate::server::ctrl_sock_from_pid(proxy_pid);
+    let _: Value = crate::server::send_raw_command(sock, "{\"command\":\"reload-certificate\"}\n")
+        .await?;
+    Ok(())
+}
-- 
2.20.1





^ permalink raw reply	[flat|nested] 9+ messages in thread

* [pbs-devel] applied-series: [PATCH backup 0/7] hot-reload proxy certificates
  2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
                   ` (6 preceding siblings ...)
  2021-05-11 13:54 ` [pbs-devel] [PATCH backup 7/7] hot-reload proxy certificate when updating via the API Wolfgang Bumiller
@ 2021-05-11 16:11 ` Thomas Lamprecht
  7 siblings, 0 replies; 9+ messages in thread
From: Thomas Lamprecht @ 2021-05-11 16:11 UTC (permalink / raw)
  To: Proxmox Backup Server development discussion, Wolfgang Bumiller

On 11.05.21 15:53, Wolfgang Bumiller wrote:
> This adds the ability to tell a running proxy to just reload the TLS
> cert certificates via the command-socket.
> 
> Starts off with some cleanup/refactoring to get rid of all that heavy
> indentation...
> 
> Wolfgang Bumiller (7):
>   proxy: factor out accept_connection
>   proxy: "continue on error" for the accept call, too
>   proxy: Arc usage cleanup
>   proxy: factor out tls acceptor creation
>   proxy: implement 'reload-certificate' command
>   refactor send_command
>   hot-reload proxy certificate when updating via the API
> 
>  src/api2/node/certificates.rs   |  26 ++--
>  src/bin/proxmox-backup-proxy.rs | 220 ++++++++++++++++++++------------
>  src/config.rs                   |  17 +--
>  src/server.rs                   |   9 ++
>  src/server/command_socket.rs    |  71 ++++++-----
>  src/server/worker_task.rs       |   4 +-
>  6 files changed, 204 insertions(+), 143 deletions(-)
> 

applied whole series, thanks!

I followed this up with:
* fallback to "default" account on order (check commit message, was really confusing else)
* add UI task-description entries for acme related tasks, mostly based on the ones from
  PVE, but as the worker type is spelled slightly different anyway here I took the chance
  to use a slightly nicer version there too.
* set account name as worker ID so that they can be used in by the task-descriptions
  - note: all account actions done before that commit miss it and will display "default",
    even if the account was named something else, I ignored this as we did not really
    rolled this out externally yet.




^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2021-05-11 16:12 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-11 13:53 [pbs-devel] [PATCH backup 0/7] hot-reload proxy certificates Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 1/7] proxy: factor out accept_connection Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 2/7] proxy: "continue on error" for the accept call, too Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 3/7] proxy: Arc usage cleanup Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 4/7] proxy: factor out tls acceptor creation Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 5/7] proxy: implement 'reload-certificate' command Wolfgang Bumiller
2021-05-11 13:53 ` [pbs-devel] [PATCH backup 6/7] refactor send_command Wolfgang Bumiller
2021-05-11 13:54 ` [pbs-devel] [PATCH backup 7/7] hot-reload proxy certificate when updating via the API Wolfgang Bumiller
2021-05-11 16:11 ` [pbs-devel] applied-series: [PATCH backup 0/7] hot-reload proxy certificates Thomas Lamprecht

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal