ref(feat-33): TLS support for Spin.
The `spin-cli` now supports the `tls-key` and `tls-cert` options, e.g. ```spin up -tls-key <path/to/key> -tls-cert <path/to/cert> ...``` For convenience, the following environment variables can be set in lieu of their respective flags. ``` SPIN_TLS_CERT=<path/to/cert> SPIN_TLS_KEY=<path/to/key> ``` NOTE: Explicitly set flags take precedence over the environment. ``` export RUST_LOG=spin_engine=info,spin_http,wact=info export SPIN_TLS_CERT=crates/http/tests/local.crt.pem export SPIN_TLS_KEY=crates/http/tests/local.key.pem spin up --app templates/spin-http/spin.toml curl -k https://127.0.0.1:3000/test/hello ``` Signed-off-by: Brian Hardock <brian.hardock@fermyon.com> Co-authored-by: Michelle Dhanani <michelle@fermyon.com> Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
This commit is contained in:
parent
6651c2ca8d
commit
077022d173
|
@ -1298,7 +1298,9 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
|||
dependencies = [
|
||||
"http 0.2.6",
|
||||
"hyper",
|
||||
"log",
|
||||
"rustls 0.20.3",
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls 0.23.2",
|
||||
]
|
||||
|
@ -2419,7 +2421,7 @@ dependencies = [
|
|||
"percent-encoding 2.1.0",
|
||||
"pin-project-lite",
|
||||
"rustls 0.20.3",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 0.2.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
|
@ -2503,6 +2505,18 @@ dependencies = [
|
|||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 0.2.1",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.2.1"
|
||||
|
@ -2512,6 +2526,15 @@ dependencies = [
|
|||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
|
@ -2913,14 +2936,19 @@ dependencies = [
|
|||
"bytes 1.1.0",
|
||||
"ctrlc",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"http 0.2.6",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"indexmap",
|
||||
"miniserde",
|
||||
"rustls-pemfile 0.3.0",
|
||||
"serde",
|
||||
"spin-config",
|
||||
"spin-engine",
|
||||
"tls-listener",
|
||||
"tokio",
|
||||
"tokio-rustls 0.23.2",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"tracing-subscriber 0.3.8",
|
||||
|
@ -3219,6 +3247,20 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tls-listener"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42257668593ac35c772fa8bfb4bfd4d357298d5a6b816cb9a4462ec3b7627d26"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls 0.23.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.17.0"
|
||||
|
|
8
Makefile
8
Makefile
|
@ -1,4 +1,5 @@
|
|||
LOG_LEVEL ?= spin=trace
|
||||
CERT_NAME ?= local
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
|
@ -9,3 +10,10 @@ test:
|
|||
RUST_LOG=$(LOG_LEVEL) cargo test --all -- --nocapture
|
||||
cargo clippy --all-targets --all-features -- -D warnings
|
||||
cargo fmt --all -- --check
|
||||
|
||||
# simple convenience for developing with TLS
|
||||
.PHONY: tls
|
||||
tls: ${CERT_NAME}.crt.pem
|
||||
|
||||
$(CERT_NAME).crt.pem:
|
||||
openssl req -newkey rsa:2048 -nodes -keyout $(CERT_NAME).key.pem -x509 -days 365 -out $(CERT_NAME).crt.pem
|
|
@ -13,13 +13,18 @@ async-trait = "0.1"
|
|||
bytes = "1.1"
|
||||
ctrlc = "3.2.1"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3.8"
|
||||
http = "0.2"
|
||||
hyper = { version = "0.14", features = [ "full" ] }
|
||||
hyper-rustls = { version = "0.23.0" }
|
||||
indexmap = "1.6"
|
||||
serde = { version = "1.0", features = [ "derive" ] }
|
||||
spin-config = { path = "../config" }
|
||||
spin-engine = { path = "../engine" }
|
||||
tls-listener = { version = "0.4.0", features = ["rustls", "hyper-h1", "hyper-h2"] }
|
||||
tokio = { version = "1.10", features = [ "full" ] }
|
||||
tokio-rustls = { version = "0.23.2" }
|
||||
rustls-pemfile = "0.3.0"
|
||||
tracing = { version = "0.1", features = [ "log" ] }
|
||||
tracing-futures = "0.2"
|
||||
tracing-subscriber = { version = "0.3.7", features = [ "env-filter" ] }
|
||||
|
|
|
@ -2,13 +2,16 @@
|
|||
|
||||
mod routes;
|
||||
mod spin;
|
||||
mod tls;
|
||||
mod wagi;
|
||||
|
||||
use crate::wagi::WagiHttpExecutor;
|
||||
use anyhow::{Error, Result};
|
||||
use anyhow::{Context, Error, Result};
|
||||
use async_trait::async_trait;
|
||||
use http::{StatusCode, Uri};
|
||||
use futures_util::stream::StreamExt;
|
||||
use http::{uri::Scheme, StatusCode, Uri};
|
||||
use hyper::{
|
||||
server::accept,
|
||||
server::conn::AddrStream,
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Request, Response, Server,
|
||||
|
@ -18,7 +21,11 @@ use spin::SpinHttpExecutor;
|
|||
use spin_config::{ApplicationTrigger, Configuration, CoreComponent, TriggerConfig};
|
||||
use spin_engine::{Builder, ExecutionContextConfiguration};
|
||||
use spin_http::SpinHttpData;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use std::{future::ready, net::SocketAddr, sync::Arc};
|
||||
pub use tls::TlsConfig;
|
||||
use tls_listener::TlsListener;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_rustls::server::TlsStream;
|
||||
use tracing::log;
|
||||
|
||||
wit_bindgen_wasmtime::import!("wit/ephemeral/spin-http.wit");
|
||||
|
@ -27,8 +34,6 @@ type ExecutionContext = spin_engine::ExecutionContext<SpinHttpData>;
|
|||
type RuntimeContext = spin_engine::RuntimeContext<SpinHttpData>;
|
||||
|
||||
/// The Spin HTTP trigger.
|
||||
/// TODO
|
||||
/// This should contain TLS configuration.
|
||||
///
|
||||
/// Could this contain a list of multiple HTTP applications?
|
||||
/// (there could be a field apps: HashMap<String, Config>, where
|
||||
|
@ -40,6 +45,8 @@ pub struct HttpTrigger {
|
|||
pub address: String,
|
||||
/// Configuration for the application.
|
||||
pub app: Configuration<CoreComponent>,
|
||||
/// TLS configuration for the server.
|
||||
tls: Option<TlsConfig>,
|
||||
/// Router.
|
||||
router: Router,
|
||||
/// Spin execution context.
|
||||
|
@ -52,6 +59,7 @@ impl HttpTrigger {
|
|||
address: String,
|
||||
app: Configuration<CoreComponent>,
|
||||
wasmtime: Option<wasmtime::Config>,
|
||||
tls: Option<TlsConfig>,
|
||||
) -> Result<Self> {
|
||||
let mut config = ExecutionContextConfiguration::new(app.clone());
|
||||
if let Some(wasmtime) = wasmtime {
|
||||
|
@ -65,6 +73,7 @@ impl HttpTrigger {
|
|||
Ok(Self {
|
||||
address,
|
||||
app,
|
||||
tls,
|
||||
router,
|
||||
engine,
|
||||
})
|
||||
|
@ -96,76 +105,103 @@ impl HttpTrigger {
|
|||
|
||||
let res = match executor {
|
||||
spin_config::HttpExecutor::Spin => {
|
||||
SpinHttpExecutor::execute(
|
||||
&self.engine,
|
||||
&c.id,
|
||||
&app_trigger.base,
|
||||
&trigger.route,
|
||||
req,
|
||||
addr,
|
||||
&(),
|
||||
)
|
||||
.await
|
||||
let executor = SpinHttpExecutor;
|
||||
executor
|
||||
.execute(
|
||||
&self.engine,
|
||||
&c.id,
|
||||
&app_trigger.base,
|
||||
&trigger.route,
|
||||
req,
|
||||
addr,
|
||||
)
|
||||
.await
|
||||
}
|
||||
spin_config::HttpExecutor::Wagi(wagi_config) => {
|
||||
WagiHttpExecutor::execute(
|
||||
&self.engine,
|
||||
&c.id,
|
||||
&app_trigger.base,
|
||||
&trigger.route,
|
||||
req,
|
||||
addr,
|
||||
wagi_config,
|
||||
)
|
||||
.await
|
||||
let executor = WagiHttpExecutor {
|
||||
wagi_config: wagi_config.clone(),
|
||||
};
|
||||
executor
|
||||
.execute(
|
||||
&self.engine,
|
||||
&c.id,
|
||||
&app_trigger.base,
|
||||
&trigger.route,
|
||||
req,
|
||||
addr,
|
||||
)
|
||||
.await
|
||||
}
|
||||
};
|
||||
match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => {
|
||||
log::error!("Error processing request: {:?}", e);
|
||||
Ok(Self::internal_error())
|
||||
Self::internal_error(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => Ok(Self::not_found()),
|
||||
Err(_) => Self::not_found(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an HTTP 500 response.
|
||||
fn internal_error() -> Response<Body> {
|
||||
let mut err = Response::default();
|
||||
*err.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
err
|
||||
fn internal_error(body: Option<&str>) -> Result<Response<Body>> {
|
||||
let body = match body {
|
||||
Some(body) => Body::from(body.as_bytes().to_vec()),
|
||||
None => Body::empty(),
|
||||
};
|
||||
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(body)?)
|
||||
}
|
||||
|
||||
/// Create an HTTP 404 response.
|
||||
fn not_found() -> Response<Body> {
|
||||
fn not_found() -> Result<Response<Body>> {
|
||||
let mut not_found = Response::default();
|
||||
*not_found.status_mut() = StatusCode::NOT_FOUND;
|
||||
not_found
|
||||
Ok(not_found)
|
||||
}
|
||||
|
||||
/// Run the HTTP trigger indefinitely.
|
||||
pub async fn run(&self) -> Result<()> {
|
||||
match self.tls.as_ref() {
|
||||
Some(tls) => self.serve_tls(tls).await?,
|
||||
None => self.serve().await?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serve(&self) -> Result<()> {
|
||||
let mk_svc = make_service_fn(move |addr: &AddrStream| {
|
||||
let t = self.clone();
|
||||
let addr = addr.remote_addr();
|
||||
|
||||
async move {
|
||||
Ok::<_, Error>(service_fn(move |req| {
|
||||
Ok::<_, Error>(service_fn(move |mut req| {
|
||||
let t2 = t.clone();
|
||||
|
||||
async move { t2.handle(req, addr).await }
|
||||
async move {
|
||||
match set_req_uri(&mut req, Scheme::HTTPS) {
|
||||
Ok(()) => t2.handle(req, addr).await,
|
||||
Err(e) => {
|
||||
log::warn!("{}", e);
|
||||
Self::internal_error(Some("Socket connection error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let addr: SocketAddr = self.address.parse()?;
|
||||
|
||||
log::info!("Serving HTTP on address {:?}", addr);
|
||||
|
||||
let shutdown_signal = on_ctrl_c()?;
|
||||
|
||||
let addr: SocketAddr = self.address.parse()?;
|
||||
log::info!("Serving on address {:?}", addr);
|
||||
Server::bind(&addr)
|
||||
.serve(mk_svc)
|
||||
.with_graceful_shutdown(async {
|
||||
|
@ -177,6 +213,88 @@ impl HttpTrigger {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serve_tls(&self, tls: &TlsConfig) -> Result<()> {
|
||||
let mk_svc = make_service_fn(move |conn: &TlsStream<TcpStream>| {
|
||||
let (inner, _) = conn.get_ref();
|
||||
let addr_res = inner.peer_addr().map_err(|e| e.to_string());
|
||||
let t = self.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
Ok::<_, Error>(service_fn(move |mut req| {
|
||||
let t2 = t.clone();
|
||||
let a_res = addr_res.clone();
|
||||
|
||||
async move {
|
||||
match set_req_uri(&mut req, Scheme::HTTPS) {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
log::warn!("{}", e);
|
||||
return Self::internal_error(Some("Socket connection error"));
|
||||
}
|
||||
}
|
||||
|
||||
match a_res {
|
||||
Ok(addr) => t2.handle(req, addr).await,
|
||||
Err(e) => {
|
||||
log::warn!("Socket connection error on new connection: {}", e);
|
||||
Self::internal_error(Some("Socket connection error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
});
|
||||
|
||||
let addr: SocketAddr = self.address.parse()?;
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
|
||||
let tls_srv_cfg = tls.server_config()?;
|
||||
|
||||
let incoming =
|
||||
accept::from_stream(TlsListener::new(tls_srv_cfg, listener).filter(|conn| {
|
||||
if let Err(err) = conn {
|
||||
log::warn!("{:?}", err);
|
||||
ready(false)
|
||||
} else {
|
||||
ready(true)
|
||||
}
|
||||
}));
|
||||
|
||||
log::info!("Serving HTTPS on address {:?}", addr);
|
||||
|
||||
let shutdown_signal = on_ctrl_c()?;
|
||||
|
||||
Server::builder(incoming)
|
||||
.serve(mk_svc)
|
||||
.with_graceful_shutdown(async {
|
||||
shutdown_signal.await.ok();
|
||||
})
|
||||
.await?;
|
||||
|
||||
log::debug!("User requested shutdown: exiting");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_req_uri(req: &mut Request<Body>, scheme: Scheme) -> Result<()> {
|
||||
const DEFAULT_HOST: &str = "localhost";
|
||||
|
||||
let authority_hdr = req
|
||||
.headers()
|
||||
.get(http::header::HOST)
|
||||
.map(|h| h.to_str().context("Expected UTF8 header value (authority)"))
|
||||
.unwrap_or(Ok(DEFAULT_HOST))?;
|
||||
let uri = req.uri().clone();
|
||||
let mut parts = uri.into_parts();
|
||||
parts.authority = authority_hdr
|
||||
.parse()
|
||||
.map(Option::Some)
|
||||
.map_err(|e| anyhow::anyhow!("Invalid authority {:?}", e))?;
|
||||
parts.scheme = Some(scheme);
|
||||
*req.uri_mut() = Uri::from_parts(parts).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_ctrl_c() -> Result<impl std::future::Future<Output = Result<(), tokio::task::JoinError>>> {
|
||||
|
@ -203,7 +321,6 @@ pub(crate) fn default_headers(
|
|||
raw: &str,
|
||||
base: &str,
|
||||
host: &str,
|
||||
// scheme: &str,
|
||||
) -> Result<Vec<(String, String)>> {
|
||||
let mut res = vec![];
|
||||
let abs_path = uri
|
||||
|
@ -213,8 +330,8 @@ pub(crate) fn default_headers(
|
|||
|
||||
let path_info = RoutePattern::from(base, raw).relative(abs_path)?;
|
||||
|
||||
// TODO: check if TLS is enabled and change the scheme to "https".
|
||||
let scheme = "http";
|
||||
let scheme = uri.scheme_str().unwrap_or("http");
|
||||
|
||||
let full_url = format!("{}://{}{}", scheme, host, abs_path);
|
||||
let matched_route = RoutePattern::sanitize_with_base(base, raw);
|
||||
|
||||
|
@ -239,17 +356,14 @@ pub(crate) fn default_headers(
|
|||
/// All HTTP executors must implement this trait.
|
||||
#[async_trait]
|
||||
pub(crate) trait HttpExecutor: Clone + Send + Sync + 'static {
|
||||
/// Configuration specific to the implementor of this trait.
|
||||
type Config;
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
engine: &ExecutionContext,
|
||||
component: &str,
|
||||
base: &str,
|
||||
raw_route: &str,
|
||||
req: Request<Body>,
|
||||
client_addr: SocketAddr,
|
||||
config: &Self::Config,
|
||||
) -> Result<Response<Body>>;
|
||||
}
|
||||
|
||||
|
@ -310,10 +424,9 @@ mod tests {
|
|||
|
||||
let default_headers = crate::default_headers(req.uri(), trigger_route, base, host)?;
|
||||
|
||||
// TODO: we currently replace the scheme with HTTP. When TLS is supported, this should be fixed.
|
||||
assert_eq!(
|
||||
search(X_FULL_URL_HEADER, &default_headers).unwrap(),
|
||||
"http://fermyon.dev/base/foo/bar?key1=value1&key2=value2".to_string()
|
||||
"https://fermyon.dev/base/foo/bar?key1=value1&key2=value2".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
search(PATH_INFO_HEADER, &default_headers).unwrap(),
|
||||
|
@ -363,7 +476,7 @@ mod tests {
|
|||
// TODO: we currently replace the scheme with HTTP. When TLS is supported, this should be fixed.
|
||||
assert_eq!(
|
||||
search(X_FULL_URL_HEADER, &default_headers).unwrap(),
|
||||
"http://fermyon.dev/foo/bar?key1=value1&key2=value2".to_string()
|
||||
"https://fermyon.dev/foo/bar?key1=value1&key2=value2".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
search(PATH_INFO_HEADER, &default_headers).unwrap(),
|
||||
|
@ -433,7 +546,7 @@ mod tests {
|
|||
let components = vec![component];
|
||||
|
||||
let cfg = Configuration::<CoreComponent> { info, components };
|
||||
let trigger = HttpTrigger::new("".to_string(), cfg, None).await?;
|
||||
let trigger = HttpTrigger::new("".to_string(), cfg, None, None).await?;
|
||||
|
||||
let body = Body::from("Fermyon".as_bytes().to_vec());
|
||||
let req = http::Request::builder()
|
||||
|
@ -490,7 +603,7 @@ mod tests {
|
|||
let components = vec![component];
|
||||
|
||||
let cfg = Configuration::<CoreComponent> { info, components };
|
||||
let trigger = HttpTrigger::new("".to_string(), cfg, None).await?;
|
||||
let trigger = HttpTrigger::new("".to_string(), cfg, None, None).await?;
|
||||
|
||||
let body = Body::from("Fermyon".as_bytes().to_vec());
|
||||
let req = http::Request::builder()
|
||||
|
|
|
@ -15,16 +15,14 @@ pub struct SpinHttpExecutor;
|
|||
|
||||
#[async_trait]
|
||||
impl HttpExecutor for SpinHttpExecutor {
|
||||
type Config = ();
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
engine: &ExecutionContext,
|
||||
component: &str,
|
||||
base: &str,
|
||||
raw_route: &str,
|
||||
req: Request<Body>,
|
||||
_client_addr: SocketAddr,
|
||||
_config: &Self::Config,
|
||||
) -> Result<Response<Body>> {
|
||||
log::trace!(
|
||||
"Executing request using the Spin executor for component {}",
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
use std::{
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio_rustls::{rustls, TlsAcceptor};
|
||||
|
||||
/// Tls configuration for the server.
|
||||
#[derive(Clone)]
|
||||
pub struct TlsConfig {
|
||||
/// Path to TLS certificate.
|
||||
pub cert_path: PathBuf,
|
||||
/// Path to TLS key.
|
||||
pub key_path: PathBuf,
|
||||
}
|
||||
|
||||
impl TlsConfig {
|
||||
// Create a TLS acceptor from server config.
|
||||
pub(super) fn server_config(&self) -> anyhow::Result<TlsAcceptor> {
|
||||
let certs = load_certs(&self.cert_path)?;
|
||||
let mut keys = load_keys(&self.key_path)?;
|
||||
|
||||
let cfg = rustls::ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, keys.remove(0))
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
||||
Ok(Arc::new(cfg).into())
|
||||
}
|
||||
}
|
||||
|
||||
// Load public certificate from file.
|
||||
fn load_certs(path: impl AsRef<Path>) -> io::Result<Vec<rustls::Certificate>> {
|
||||
certs(&mut io::BufReader::new(fs::File::open(path)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert"))
|
||||
.map(|mut certs| certs.drain(..).map(rustls::Certificate).collect())
|
||||
}
|
||||
|
||||
// Load private key from file.
|
||||
fn load_keys(path: impl AsRef<Path>) -> io::Result<Vec<rustls::PrivateKey>> {
|
||||
pkcs8_private_keys(&mut io::BufReader::new(fs::File::open(path)?))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key"))
|
||||
.map(|mut keys| keys.drain(..).map(rustls::PrivateKey).collect())
|
||||
}
|
|
@ -16,20 +16,20 @@ use tracing::log;
|
|||
use wasi_common::pipe::{ReadPipe, WritePipe};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WagiHttpExecutor;
|
||||
pub struct WagiHttpExecutor {
|
||||
pub wagi_config: WagiConfig,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl HttpExecutor for WagiHttpExecutor {
|
||||
type Config = WagiConfig;
|
||||
|
||||
async fn execute(
|
||||
&self,
|
||||
engine: &ExecutionContext,
|
||||
component: &str,
|
||||
base: &str,
|
||||
raw_route: &str,
|
||||
req: Request<Body>,
|
||||
client_addr: SocketAddr,
|
||||
wagi_config: &Self::Config,
|
||||
) -> Result<Response<Body>> {
|
||||
log::trace!(
|
||||
"Executing request using the Wagi executor for component {}",
|
||||
|
@ -72,6 +72,7 @@ impl HttpExecutor for WagiHttpExecutor {
|
|||
.unwrap_or(&default_host)
|
||||
.as_bytes(),
|
||||
)?;
|
||||
|
||||
// Add the default Spin headers.
|
||||
// Note that this overrides any existing headers previously set by Wagi.
|
||||
for (k, v) in crate::default_headers(&parts.uri, raw_route, base, host)? {
|
||||
|
@ -87,11 +88,11 @@ impl HttpExecutor for WagiHttpExecutor {
|
|||
)?;
|
||||
|
||||
let start = instance
|
||||
.get_func(&mut store, &wagi_config.entrypoint)
|
||||
.get_func(&mut store, &self.wagi_config.entrypoint)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"No such function '{}' in {}",
|
||||
wagi_config.entrypoint,
|
||||
self.wagi_config.entrypoint,
|
||||
component
|
||||
)
|
||||
})?;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICujCCAaICCQClexHj2O4K/TANBgkqhkiG9w0BAQsFADAfMQswCQYDVQQGEwJV
|
||||
UzEQMA4GA1UECgwHRmVybXlvbjAeFw0yMjAyMjUxNzQ3MTFaFw0yMzAyMjUxNzQ3
|
||||
MTFaMB8xCzAJBgNVBAYTAlVTMRAwDgYDVQQKDAdGZXJteW9uMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwMbUZ2eoIaJfgcBJ2fILUViWYApnA9SU+Ruf
|
||||
nm6DNm9Gy5+YThqxd/0mhbPwYVkfi2/3UddWDl3VPOAYcvYoHDqH0tHm10wo+UzY
|
||||
DDcNZB9enLRfGCv9Fful4bqNd3Vtx2xNwc8+F0WiljtYeMc+9wp7M5WWbKJqzKPe
|
||||
VQBADRlfGoG3jCLGaQ2fyVp/73nWdqbbluWJopxHph7v1alb/BxLcDi/tjWKgZut
|
||||
Vr9ZtBBPDSjRbfjHarn6pibYZAWgzanpfsaSBdbpVNn1MQ/gNXIHmNFwfbsN0V+3
|
||||
LN/Z4VNZrkc+C7CjGhJOcBj0xtrSDhoHnOmDS/z+lBUdlNOUrQIDAQABMA0GCSqG
|
||||
SIb3DQEBCwUAA4IBAQAOnRPnUJoEE8s9+ADUpKkWBXFCiRajtBSBDNDX3phRPwly
|
||||
q2zG+gXyV+Axx1qvsis9yXQBF9DcD+lx0rEgGzQjYGfmEA45E8Co2Tih2ON7JkCu
|
||||
bYoT+wMkgfOMci/S2BBOJ+d0LI3K0b1qDfc4KwHe6g3p5ywuEBFOaWKiMemJyywd
|
||||
zpoD6QmcQ9qlp5/2pf12bNRUIdXe5+vMU3qVIZcWM49u04L2/Swyc6EFXfEtnp/m
|
||||
6184isfCkc3egMvqEfrKUaf0lgNzCksmRD9sLF8wWaV4lcidzsNDdU47EPFutVMU
|
||||
3iLgXAhmRuZ+eoBf56QkzVTQWnCYQdlGwZp1Fcoj
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAxtRnZ6ghol+B
|
||||
wEnZ8gtRWJZgCmcD1JT5G5+eboM2b0bLn5hOGrF3/SaFs/BhWR+Lb/dR11YOXdU8
|
||||
4Bhy9igcOofS0ebXTCj5TNgMNw1kH16ctF8YK/0V+6Xhuo13dW3HbE3Bzz4XRaKW
|
||||
O1h4xz73CnszlZZsomrMo95VAEANGV8agbeMIsZpDZ/JWn/vedZ2ptuW5YminEem
|
||||
Hu/VqVv8HEtwOL+2NYqBm61Wv1m0EE8NKNFt+MdqufqmJthkBaDNqel+xpIF1ulU
|
||||
2fUxD+A1cgeY0XB9uw3RX7cs39nhU1muRz4LsKMaEk5wGPTG2tIOGgec6YNL/P6U
|
||||
FR2U05StAgMBAAECggEAfpcSjATJp6yUwwOee3wyamyd8tth4mYKnbrCCqvPhkN0
|
||||
XeqjfUaSG5UlYs9SntqDmHEiG6AoZq6/hIY0B+oVVNQqtQoZaHAex/bqOLs+E+11
|
||||
l7nqaFkajQD/YUe79iIqwLYiKY8J2wZjSfwWkNlmQ5uiY7FrYlMVhuRk77SGWxKW
|
||||
UbWfgTTMgEWIK6bU77FShQ7b0px5ZIulRPQeRaH8USdx0yktqUMwUakIrNyZ64u+
|
||||
Gx9k4ma2bCmbWxGlCEp0EQsYOlWDBeKu3Elq2g48KmADzbjvKlS7S/0fhcVqi2dE
|
||||
Fj0BrmzxWjPzJwqxA6Z/8tykqzL5Nr6tOm0e6ZhBEQKBgQDhfy83jLfIWLt3rMcx
|
||||
dFA4TGFSEUVJE9ESV0Za5zriLeGzM66JGut+Bph9Kk7XmDt+q3ewFJv7oDVibhzG
|
||||
4nit+TakfSMWUAronsf2wQuUvpE6rNoZlWjhd7AE5f/eBZTYhNm5cp7ujGwnEn47
|
||||
vmfSVev+1yQcEUeV10OSWWaCrwKBgQDa2pEwps6htnZqiZsJP86LfxbTA1P+BgsV
|
||||
nFvVkcCT0Uy7V0pSSdA82Ua/1KfcQ3BAJiBkINSL6Sob1+3lSQTeTHLVbXySacnh
|
||||
c7UDDoayWJxtYNyjJeBzrjlZCDIkipJqz26pGfIhxePwVgbj30O/EB55y44gkxqn
|
||||
JIvqIWBlYwKBgQDVqR4DI3lMAw92QKbo7A3KmkyoZybgLD+wgjNulKQNhW3Sz4hz
|
||||
7qbt3bAFAN59l4ff6PZaR9zYWh/bKPxpUlMIfRdSWiOx05vSeAh+fMHNaZfQIdHx
|
||||
5cjfwfltWsTLCTzUv2RRPBLtcu5TQ0mKsEpNWQ5ohE95rMHIb5ReCgmAjwKBgCb6
|
||||
NlGL49E5Re3DhDEphAekItSCCzt6qA65QkHPK5Un+ZqD+WCedM/hgpA3t42rFRrX
|
||||
r30lu7UPWciLtHrZflx5ERqh3UXWQXY9vUdGFwc8cN+qGKGV5Vu089G/e+62H02W
|
||||
lAbZ8B3DuMzdBW0gHliw7jyS3EVA7cZG5ARW3WwxAoGAW+FkrJKsPyyScHBdu/LD
|
||||
GeDMGRBRBdthXVbtB7xkzi2Tla4TywlHTm32rK3ywtoBxzvhlxbVnbBODMO/83xZ
|
||||
DKjq2leuKfXUNsuMEre7uhhs7ezEM6QfiKTTosD/D8Z3S8AA4q1NKu3iEBUjtXcS
|
||||
FSaIdbf6aHPcvbRB9cDv5ho=
|
||||
-----END PRIVATE KEY-----
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{bail, Result};
|
||||
use spin_config::{Configuration, CoreComponent};
|
||||
use spin_http_engine::HttpTrigger;
|
||||
use spin_http_engine::{HttpTrigger, TlsConfig};
|
||||
use std::path::PathBuf;
|
||||
use structopt::{clap::AppSettings, StructOpt};
|
||||
|
||||
|
@ -9,6 +9,12 @@ const BINDLE_ID_OPT: &str = "BINDLE_ID";
|
|||
const BINDLE_SERVER_URL_OPT: &str = "BINDLE_SERVER_URL";
|
||||
const BINDLE_URL_ENV: &str = "BINDLE_URL";
|
||||
|
||||
const TLS_CERT_FILE_OPT: &str = "TLS_CERT_FILE";
|
||||
const TLS_KEY_FILE_OPT: &str = "TLS_KEY_FILE";
|
||||
|
||||
const TLS_CERT_ENV_VAR: &str = "SPIN_TLS_CERT";
|
||||
const TLS_KEY_ENV_VAR: &str = "SPIN_TLS_KEY";
|
||||
|
||||
/// Start the Fermyon HTTP runtime.
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(
|
||||
|
@ -50,6 +56,24 @@ pub struct UpCommand {
|
|||
/// Pass an environment variable (key=value) to all components of the application.
|
||||
#[structopt(long = "env", short = "e", parse(try_from_str = parse_env_var))]
|
||||
env: Vec<(String, String)>,
|
||||
|
||||
/// The path to the certificate to use for https, if this is not set, normal http will be used. The cert should be in PEM format
|
||||
#[structopt(
|
||||
name = TLS_CERT_FILE_OPT,
|
||||
long = "tls-cert",
|
||||
env = TLS_CERT_ENV_VAR,
|
||||
requires = TLS_KEY_FILE_OPT,
|
||||
)]
|
||||
pub tls_cert: Option<PathBuf>,
|
||||
|
||||
/// The path to the certificate key to use for https, if this is not set, normal http will be used. The key should be in PKCS#8 format
|
||||
#[structopt(
|
||||
name = TLS_KEY_FILE_OPT,
|
||||
long = "tls-key",
|
||||
env = TLS_KEY_ENV_VAR,
|
||||
requires = TLS_CERT_FILE_OPT,
|
||||
)]
|
||||
pub tls_key: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl UpCommand {
|
||||
|
@ -65,7 +89,24 @@ impl UpCommand {
|
|||
};
|
||||
append_env(&mut app, &self.env)?;
|
||||
|
||||
let trigger = HttpTrigger::new(self.address, app, None).await?;
|
||||
let tls = match (self.tls_key, self.tls_cert) {
|
||||
(Some(key_path), Some(cert_path)) => {
|
||||
if !cert_path.is_file() {
|
||||
bail!("TLS certificate file does not exist or is not a file")
|
||||
}
|
||||
if !key_path.is_file() {
|
||||
bail!("TLS key file does not exist or is not a file")
|
||||
}
|
||||
Some(TlsConfig {
|
||||
cert_path,
|
||||
key_path,
|
||||
})
|
||||
}
|
||||
(None, None) => None,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let trigger = HttpTrigger::new(self.address, app, None, tls).await?;
|
||||
trigger.run().await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -386,7 +386,7 @@ mod integration_tests {
|
|||
Ok(_) => break,
|
||||
Err(_) => {
|
||||
wait_count += 1;
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
sleep(Duration::from_secs(120)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue