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:
Brian Hardock 2022-02-28 11:11:17 -07:00
parent 6651c2ca8d
commit 077022d173
No known key found for this signature in database
GPG Key ID: 4819C9ADD22D4898
11 changed files with 361 additions and 62 deletions

44
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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" ] }

View File

@ -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()

View File

@ -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 {}",

46
crates/http/src/tls.rs Normal file
View File

@ -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())
}

View File

@ -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
)
})?;

View File

@ -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-----

View File

@ -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-----

View File

@ -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
}
}

View File

@ -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;
}
}
}