main.rs (5980B)
1 2 use axum::{ 3 extract::Request, 4 http::{HeaderMap,HeaderValue}, 5 routing::get, Router, 6 http::StatusCode 7 }; 8 use std::sync::Arc; 9 use std::net::SocketAddr; 10 11 use std::process::Command; 12 13 use hyper::body::Incoming; 14 use hyper_util::rt::{TokioExecutor, TokioIo}; 15 use tokio::net::TcpListener; 16 use tokio_rustls::{ 17 rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer}, 18 rustls::ServerConfig, 19 TlsAcceptor, 20 }; 21 use rustls::{ 22 server::WebPkiClientVerifier, 23 RootCertStore 24 }; 25 use serde_yaml::Value as YamlValue; 26 27 use tower_service::Service; 28 29 use clap::Parser; 30 use log::{error,warn}; 31 32 const JSON_MIME : HeaderValue = HeaderValue::from_static("application/json"); 33 34 #[derive(Parser)] 35 #[command(name = "undercroft")] 36 #[command(version = "0.1")] 37 #[command(about = "serves sops secrets to certified https clients", long_about = None)] 38 39 struct Cli { 40 #[arg(long)] 41 ca_certificate: String, 42 #[arg(long)] 43 certificate: String, 44 #[arg(long)] 45 key: String, 46 #[arg(long)] 47 port: Option<u16>, 48 #[arg(long)] 49 bind: Option<String>, 50 secrets: String 51 } 52 53 fn yaml_to_json(content: &str) -> Result<String, &str> { 54 match serde_yaml::from_str::<YamlValue>(content) { 55 Ok(v) => { 56 match serde_json::to_string_pretty(&v) { 57 Ok(s) => Ok(s), 58 Err(e) => { 59 println!("error {:?}", e); 60 Err("can't json") 61 } 62 } 63 }, 64 Err(e) => { 65 println!("error {:?}", e); 66 Err("can't yaml") 67 } 68 } 69 } 70 71 72 73 async fn send_it(headers: HeaderMap, secrets_path : String) -> (StatusCode, String) { 74 let output = Command::new("sops") 75 .arg("decrypt") 76 .arg(secrets_path) 77 .output() 78 .expect("failed to execute process"); 79 80 if output.status.success() { 81 let body = String::from_utf8(output.stdout).expect("bad utf8"); 82 match headers.get("accept") { 83 Some(ctype) if ctype == &JSON_MIME => { 84 // this is a cheat as we only look for a single mime type 85 // and we only look in the first Accept header, so you 86 // can easily not get json if you ask for any other content-type 87 // as well 88 89 if let Ok(j) = yaml_to_json(&body) { 90 (StatusCode::OK, j) 91 } else { 92 (StatusCode::INTERNAL_SERVER_ERROR, "serialize error".to_string()) 93 } 94 } 95 _ => { 96 (StatusCode::OK, body) 97 } 98 } 99 } else { 100 println!("error calling sops: {:}", 101 String::from_utf8(output.stderr).expect("bad utf8")); 102 (StatusCode::INTERNAL_SERVER_ERROR, 103 "error calling sops".to_string()) 104 } 105 106 } 107 108 109 #[tokio::main] 110 async fn main() { 111 let cli = Cli::parse(); 112 println!("secrets path = {:}", cli.secrets); 113 114 let addr : SocketAddr = match cli.bind { 115 Some(s) => s.parse().expect("can't parse bind address"), 116 None => { 117 let port = cli.port.unwrap_or(3000); 118 SocketAddr::from(([127, 0, 0, 1], port)) 119 } 120 }; 121 122 let private_key = PrivateKeyDer::from_pem_file(cli.key).unwrap(); 123 124 let certs = CertificateDer::pem_file_iter(cli.certificate) 125 .unwrap() 126 .map(|cert| cert.unwrap()) 127 .collect(); 128 129 let ca_cert = CertificateDer::pem_file_iter(cli.ca_certificate) 130 .unwrap() 131 .map(|cert| cert.unwrap()) 132 .next(); 133 134 let mut root_cert_store = RootCertStore::empty(); 135 if let Some(c) = ca_cert { 136 root_cert_store.add(c).expect("bad ca certificate"); 137 }; 138 let client_cert_verifier = 139 WebPkiClientVerifier::builder(root_cert_store.into()) 140 .build() 141 .unwrap(); 142 143 let mut tls_config = 144 ServerConfig::builder() 145 .with_client_cert_verifier(client_cert_verifier) 146 .with_single_cert(certs, private_key) 147 .expect("bad certificate/key"); 148 149 tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; 150 151 let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config)); 152 153 let tcp_listener = TcpListener::bind(addr).await.unwrap(); 154 155 println!("listening on {}", addr); 156 157 let handler = move |headers: HeaderMap| send_it(headers, cli.secrets) ; 158 let app = Router::new().route("/", get(handler)); 159 160 loop { 161 let tower_service = app.clone(); 162 let tls_acceptor = tls_acceptor.clone(); 163 164 // Wait for new tcp connection 165 let (cnx, addr) = tcp_listener.accept().await.unwrap(); 166 167 tokio::spawn(async move { 168 // Wait for tls handshake to happen 169 let Ok(stream) = tls_acceptor.accept(cnx).await else { 170 error!("error during tls handshake connection from {}", addr); 171 return; 172 }; 173 174 // Hyper has its own `AsyncRead` and `AsyncWrite` traits 175 // and doesn't use tokio. `TokioIo` converts between 176 // them. 177 let stream = TokioIo::new(stream); 178 179 // Hyper also has its own `Service` trait and doesn't use 180 // tower. We can use `hyper::service::service_fn` to 181 // create a hyper `Service` that calls our app through 182 // `tower::Service::call`. 183 let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| { 184 185 // We have to clone `tower_service` because hyper's 186 // `Service` uses `&self` whereas 187 // tower's `Service` requires `&mut self`. 188 // 189 // We don't need to call `poll_ready` since `Router` 190 // is always ready. 191 tower_service.clone().call(request) 192 }); 193 194 let ret = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()) 195 .serve_connection_with_upgrades(stream, hyper_service) 196 .await; 197 if let Err(err) = ret { 198 warn!("error serving connection from {}: {}", addr, err); 199 } 200 }); 201 } 202 }