diff --git a/Cargo.lock b/Cargo.lock index 15a76aa2324..d11a8c82896 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1368,9 +1368,11 @@ dependencies = [ "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "oak_runtime 0.1.0", "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "signal-hook 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "tonic 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/oak/proto/application.proto b/oak/proto/application.proto index dd418a98522..0d1e2d2e4a9 100644 --- a/oak/proto/application.proto +++ b/oak/proto/application.proto @@ -81,10 +81,6 @@ message GrpcServerConfiguration { // The endpoint address for the gRPC server to listen on. // `address` is represented as an "ip_address:tcp_port" string. string address = 1; - // Loaded private RSA key file used by a gRPC server pseudo-Node. - string grpc_tls_private_key = 2; - // Loaded PEM encoded X.509 TLS certificate file used by a gRPC server pseudo-Node. - string grpc_tls_certificate = 3; } // GrpcClientConfiguration describes the configuration of a gRPC client @@ -94,12 +90,9 @@ message GrpcClientConfiguration { // The URI component of a gRPC server endpoint. Must contain the "Host" element. // https://docs.rs/tonic/0.2.1/tonic/transport/struct.Uri.html string uri = 1; - // Loaded PEM encoded X.509 TLS root certificate file used to authenticate an external gRPC - // service. - string root_tls_certificate = 2; // The endpoint address of the external gRPC service. // `address` is represented as an "ip_address:tcp_port" string. - string address = 3; + string address = 2; } // RoughtimeClientConfiguration describes the configuration of a Roughtime diff --git a/oak/server/rust/oak_loader/Cargo.toml b/oak/server/rust/oak_loader/Cargo.toml index 55eb92e4324..9e0ba1b5e3d 100644 --- a/oak/server/rust/oak_loader/Cargo.toml +++ b/oak/server/rust/oak_loader/Cargo.toml @@ -17,9 +17,13 @@ anyhow = "*" log = "*" oak_runtime = "=0.1.0" prost = "*" +rustls = "*" signal-hook = "*" simple_logger = "*" structopt = "*" +# Using an old version that is supported by `cargo-raze`: +# https://github.com/google/cargo-raze/issues/41#issuecomment-592274128 +tonic = { version = "=0.1.1", features = ["tls"] } [dev-dependencies] maplit = "*" diff --git a/oak/server/rust/oak_loader/src/main.rs b/oak/server/rust/oak_loader/src/main.rs index b4fe4a79ada..546d2bc1c06 100644 --- a/oak/server/rust/oak_loader/src/main.rs +++ b/oak/server/rust/oak_loader/src/main.rs @@ -39,14 +39,12 @@ use std::{ }, }; use structopt::StructOpt; +use tonic::transport::{Certificate, Identity}; #[cfg(test)] mod tests; -use oak_runtime::proto::oak::application::{ - node_configuration::ConfigType::{GrpcClientConfig, GrpcServerConfig}, - ConfigMap, -}; +use oak_runtime::proto::oak::application::ConfigMap; #[derive(StructOpt, Clone, Debug)] #[structopt(about = "Oak Loader")] @@ -133,6 +131,18 @@ pub fn parse_config_map(config_files: &[ConfigEntry]) -> anyhow::Result anyhow::Result { + use rustls::internal::pemfile::certs; + + let mut cursor = std::io::Cursor::new(certificate); + // `rustls` doesn't specify certificate parsing errors: + // https://docs.rs/rustls/0.17.0/rustls/internal/pemfile/fn.certs.html + certs(&mut cursor).map_err(|()| anyhow!("could not parse TLS certificate"))?; + + Ok(Certificate::from_pem(certificate)) +} + fn main() -> anyhow::Result<()> { if cfg!(feature = "oak_debug") { simple_logger::init_by_env(); @@ -150,45 +160,39 @@ fn main() -> anyhow::Result<()> { // Load application configuration. let app_config_data = read_file(&opt.application)?; - let mut app_config = ApplicationConfiguration::decode(app_config_data.as_ref())?; + let application_configuration = ApplicationConfiguration::decode(app_config_data.as_ref())?; // Assign a TLS identity to all gRPC server and client nodes in the application configuration. let grpc_tls_private_key = read_to_string(&opt.grpc_tls_private_key)?; let grpc_tls_certificate = read_to_string(&opt.grpc_tls_certificate)?; let root_tls_certificate = read_to_string(&opt.root_tls_certificate)?; - for node in &mut app_config.node_configs { - if let Some(GrpcServerConfig(ref mut grpc_server_config)) = node.config_type { - grpc_server_config.grpc_tls_private_key = grpc_tls_private_key.clone(); - grpc_server_config.grpc_tls_certificate = grpc_tls_certificate.clone(); - } else if let Some(GrpcClientConfig(ref mut grpc_client_config)) = node.config_type { - grpc_client_config.root_tls_certificate = root_tls_certificate.clone(); - } - } // Create Runtime config. - #[cfg(feature = "oak_debug")] - let runtime_config = oak_runtime::RuntimeConfiguration { - metrics_port: if opt.no_metrics { - None - } else { + let runtime_configuration = oak_runtime::RuntimeConfiguration { + metrics_port: if cfg!(feature = "oak_debug") && !opt.no_metrics { Some(opt.metrics_port) - }, - introspect_port: if opt.no_introspect { - None } else { + None + }, + introspect_port: if cfg!(feature = "oak_debug") && !opt.no_introspect { Some(opt.introspect_port) + } else { + None }, }; - #[cfg(not(feature = "oak_debug"))] - let runtime_config = oak_runtime::RuntimeConfiguration { - metrics_port: None, - introspect_port: None, + let grpc_configuration = oak_runtime::GrpcConfiguration { + grpc_server_tls_identity: Identity::from_pem(grpc_tls_certificate, grpc_tls_private_key), + grpc_client_root_tls_certificate: load_certificate(&root_tls_certificate)?, }; // Start the Runtime from the given config. - info!("starting Runtime, config {:?}", runtime_config); - let (runtime, initial_handle) = configure_and_run(app_config, runtime_config) - .map_err(|status| anyhow!("status {:?}", status))?; + info!("starting Runtime, config {:?}", runtime_configuration); + let (runtime, initial_handle) = configure_and_run( + application_configuration, + runtime_configuration, + grpc_configuration, + ) + .map_err(|status| anyhow!("status {:?}", status))?; info!( "initial node {:?} with write handle {:?}", runtime.node_id, initial_handle diff --git a/oak/server/rust/oak_runtime/src/config.rs b/oak/server/rust/oak_runtime/src/config.rs index e4c510175c0..ef2e01cdb45 100644 --- a/oak/server/rust/oak_runtime/src/config.rs +++ b/oak/server/rust/oak_runtime/src/config.rs @@ -16,7 +16,7 @@ use crate::{ node, - node::{check_uri, load_certificate, load_wasm}, + node::{check_uri, load_wasm}, proto::oak::application::{ node_configuration::ConfigType, ApplicationConfiguration, GrpcClientConfiguration, GrpcServerConfiguration, LogConfiguration, NodeConfiguration, RoughtimeClientConfiguration, @@ -28,7 +28,6 @@ use itertools::Itertools; use log::error; use oak_abi::OakStatus; use std::collections::HashMap; -use tonic::transport::Identity; /// Create an application configuration. /// @@ -89,43 +88,32 @@ pub fn from_protobuf( return Err(OakStatus::ErrInvalidArgs); } Some(ConfigType::LogConfig(_)) => node::Configuration::LogNode, - Some(ConfigType::GrpcServerConfig(GrpcServerConfiguration { - address, - grpc_tls_private_key, - grpc_tls_certificate, - })) => node::Configuration::GrpcServerNode { - address: address.parse().map_err(|error| { - error!("Incorrect gRPC server address: {:?}", error); - OakStatus::ErrInvalidArgs - })?, - tls_identity: Identity::from_pem(grpc_tls_certificate, grpc_tls_private_key), - }, - Some(ConfigType::GrpcClientConfig(GrpcClientConfiguration { - uri, - root_tls_certificate, - address, - })) => node::Configuration::GrpcClientNode { - uri: uri - .parse() - .map_err(|error| { - error!("Error parsing URI {}: {:?}", uri, error); + Some(ConfigType::GrpcServerConfig(GrpcServerConfiguration { address })) => { + node::Configuration::GrpcServerNode { + address: address.parse().map_err(|error| { + error!("Incorrect gRPC server address: {:?}", error); OakStatus::ErrInvalidArgs - }) - .and_then(|uri| match check_uri(&uri) { - Ok(_) => Ok(uri), - Err(error) => { - error!("Incorrect URI {}: {:?}", uri, error); - Err(OakStatus::ErrInvalidArgs) - } })?, - root_tls_certificate: load_certificate(root_tls_certificate).map_err( - |error| { - error!("Error loading root certificate: {:?}", error); - OakStatus::ErrInvalidArgs - }, - )?, - address: address.to_string(), - }, + } + } + Some(ConfigType::GrpcClientConfig(GrpcClientConfiguration { uri, address })) => { + node::Configuration::GrpcClientNode { + uri: uri + .parse() + .map_err(|error| { + error!("Error parsing URI {}: {:?}", uri, error); + OakStatus::ErrInvalidArgs + }) + .and_then(|uri| match check_uri(&uri) { + Ok(_) => Ok(uri), + Err(error) => { + error!("Incorrect URI {}: {:?}", uri, error); + Err(OakStatus::ErrInvalidArgs) + } + })?, + address: address.to_string(), + } + } Some(ConfigType::WasmConfig(WebAssemblyConfiguration { module_bytes, .. })) => { load_wasm(&module_bytes).map_err(|error| { error!("Error loading Wasm module: {}", error); @@ -151,11 +139,12 @@ pub fn from_protobuf( /// passing the write [`oak_abi::Handle`] into the runtime will enable messages to be read /// back out from the [`RuntimeProxy`]. pub fn configure_and_run( - app_config: ApplicationConfiguration, - runtime_config: crate::RuntimeConfiguration, + application_configuration: ApplicationConfiguration, + runtime_configuration: crate::RuntimeConfiguration, + grpc_configuration: crate::GrpcConfiguration, ) -> Result<(RuntimeProxy, oak_abi::Handle), OakStatus> { - let configuration = from_protobuf(app_config)?; - let proxy = RuntimeProxy::create_runtime(configuration); - let handle = proxy.start_runtime(runtime_config)?; + let configuration = from_protobuf(application_configuration)?; + let proxy = RuntimeProxy::create_runtime(configuration, grpc_configuration); + let handle = proxy.start_runtime(runtime_configuration)?; Ok((proxy, handle)) } diff --git a/oak/server/rust/oak_runtime/src/lib.rs b/oak/server/rust/oak_runtime/src/lib.rs index cb4c6070acf..d0815262b28 100644 --- a/oak/server/rust/oak_runtime/src/lib.rs +++ b/oak/server/rust/oak_runtime/src/lib.rs @@ -32,8 +32,9 @@ pub mod metrics; pub mod node; pub mod runtime; -pub use config::{application_configuration, configure_and_run}; +use tonic::transport::{Certificate, Identity}; +pub use config::{application_configuration, configure_and_run}; pub use message::NodeMessage; pub use runtime::{NodeId, RuntimeProxy}; @@ -46,11 +47,14 @@ pub struct RuntimeConfiguration { pub introspect_port: Option, } -impl Default for RuntimeConfiguration { - fn default() -> Self { - RuntimeConfiguration { - metrics_port: None, - introspect_port: None, - } - } +/// Configuration options related to gRPC pseudo-Nodes. +/// +/// `Debug` is intentionally not implemented in order to avoid accidentally logging secrets. +pub struct GrpcConfiguration { + /// TLS identity to use for all gRPC Server Nodes. + pub grpc_server_tls_identity: Identity, + + /// Root TLS certificate to use for all gRPC Client Nodes. + // TODO(#999): Remove user-configurable root CA. + pub grpc_client_root_tls_certificate: Certificate, } diff --git a/oak/server/rust/oak_runtime/src/node/mod.rs b/oak/server/rust/oak_runtime/src/node/mod.rs index fab8b78f4a7..e6fd5ece3e1 100644 --- a/oak/server/rust/oak_runtime/src/node/mod.rs +++ b/oak/server/rust/oak_runtime/src/node/mod.rs @@ -14,7 +14,7 @@ // limitations under the License. // -use crate::runtime::RuntimeProxy; +use crate::{runtime::RuntimeProxy, GrpcConfiguration}; use log::debug; use std::{ net::{AddrParseError, SocketAddr}, @@ -22,7 +22,7 @@ use std::{ sync::Arc, }; use tokio::sync::oneshot; -use tonic::transport::{Certificate, Identity, Uri}; +use tonic::transport::Uri; pub mod external; mod grpc; @@ -57,14 +57,12 @@ pub enum Configuration { /// a TLS identity that consists of a private RSA key and an X.509 TLS certificate. GrpcServerNode { address: SocketAddr, - tls_identity: Identity, }, /// The configuration for a gRPC server pseudo-Node that contains a URI and an X.509 root TLS /// certificate. GrpcClientNode { uri: Uri, - root_tls_certificate: Certificate, address: String, }, @@ -148,18 +146,6 @@ pub fn check_uri(uri: &Uri) -> Result<(), ConfigurationError> { .ok_or(ConfigurationError::NoHostElement) } -/// Check the correctness of a PEM encoded TLS certificate. -pub fn load_certificate(certitiface: &str) -> Result { - use rustls::internal::pemfile::certs; - - let mut cursor = std::io::Cursor::new(certitiface); - // `rustls` doesn't specify certificate parsing errors: - // https://docs.rs/rustls/0.17.0/rustls/internal/pemfile/fn.certs.html - certs(&mut cursor).map_err(|_| ConfigurationError::CertificateParsingError)?; - - Ok(Certificate::from_pem(certitiface)) -} - impl Configuration { /// Creates a new Node instance corresponding to the [`Configuration`]. /// @@ -169,6 +155,7 @@ impl Configuration { node_name: &str, // Used for pretty debugging config_name: &str, entrypoint: String, + grpc_configuration: &GrpcConfiguration, ) -> Option> { debug!( "create_node('{}': '{}'.'{}')", @@ -176,23 +163,20 @@ impl Configuration { ); match self { Configuration::LogNode => Some(Box::new(logger::LogNode::new(node_name))), - Configuration::GrpcServerNode { - address, - tls_identity, - } => Some(Box::new(grpc::server::GrpcServerNode::new( - node_name, - *address, - tls_identity.clone(), - ))), - Configuration::GrpcClientNode { - uri, - root_tls_certificate, - address: _, - } => Some(Box::new(grpc::client::GrpcClientNode::new( - node_name, - uri.clone(), - root_tls_certificate.clone(), - ))), + Configuration::GrpcServerNode { address } => { + Some(Box::new(grpc::server::GrpcServerNode::new( + node_name, + *address, + grpc_configuration.grpc_server_tls_identity.clone(), + ))) + } + Configuration::GrpcClientNode { uri, address: _ } => { + Some(Box::new(grpc::client::GrpcClientNode::new( + node_name, + uri.clone(), + grpc_configuration.grpc_client_root_tls_certificate.clone(), + ))) + } Configuration::WasmNode { module } => { match wasm::WasmNode::new(node_name, module.clone(), entrypoint) { Some(node) => Some(Box::new(node)), diff --git a/oak/server/rust/oak_runtime/src/proto/oak.application.rs b/oak/server/rust/oak_runtime/src/proto/oak.application.rs index 5190981fabe..63a4453463f 100644 --- a/oak/server/rust/oak_runtime/src/proto/oak.application.rs +++ b/oak/server/rust/oak_runtime/src/proto/oak.application.rs @@ -82,12 +82,6 @@ pub struct GrpcServerConfiguration { /// `address` is represented as an "ip_address:tcp_port" string. #[prost(string, tag="1")] pub address: std::string::String, - /// Loaded private RSA key file used by a gRPC server pseudo-Node. - #[prost(string, tag="2")] - pub grpc_tls_private_key: std::string::String, - /// Loaded PEM encoded X.509 TLS certificate file used by a gRPC server pseudo-Node. - #[prost(string, tag="3")] - pub grpc_tls_certificate: std::string::String, } /// GrpcClientConfiguration describes the configuration of a gRPC client /// pseudo-Node (which is provided by the Oak Runtime), connected to a specific @@ -98,13 +92,9 @@ pub struct GrpcClientConfiguration { /// https://docs.rs/tonic/0.2.1/tonic/transport/struct.Uri.html #[prost(string, tag="1")] pub uri: std::string::String, - /// Loaded PEM encoded X.509 TLS root certificate file used to authenticate an external gRPC - /// service. - #[prost(string, tag="2")] - pub root_tls_certificate: std::string::String, /// The endpoint address of the external gRPC service. /// `address` is represented as an "ip_address:tcp_port" string. - #[prost(string, tag="3")] + #[prost(string, tag="2")] pub address: std::string::String, } /// RoughtimeClientConfiguration describes the configuration of a Roughtime diff --git a/oak/server/rust/oak_runtime/src/runtime/mod.rs b/oak/server/rust/oak_runtime/src/runtime/mod.rs index b4f48a850dc..0edffc82669 100644 --- a/oak/server/rust/oak_runtime/src/runtime/mod.rs +++ b/oak/server/rust/oak_runtime/src/runtime/mod.rs @@ -19,6 +19,7 @@ use crate::{ metrics::Metrics, node, runtime::channel::{with_reader_channel, with_writer_channel, Channel}, + GrpcConfiguration, }; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering::SeqCst}; use itertools::Itertools; @@ -249,7 +250,9 @@ impl Drop for AuxServer { /// Runtime structure for configuring and running a set of Oak Nodes. pub struct Runtime { - configuration: Configuration, + application_configuration: Configuration, + grpc_configuration: GrpcConfiguration, + terminating: AtomicBool, next_channel_id: AtomicU64, @@ -343,9 +346,13 @@ impl Runtime { // Methods on `RuntimeProxy` for managing the core `Runtime` instance. impl RuntimeProxy { /// Creates a [`Runtime`] instance with a single initial Node configured, and no channels. - pub fn create_runtime(configuration: Configuration) -> RuntimeProxy { + pub fn create_runtime( + configuration: Configuration, + grpc_configuration: GrpcConfiguration, + ) -> RuntimeProxy { let runtime = Arc::new(Runtime { - configuration, + application_configuration: configuration, + grpc_configuration, terminating: AtomicBool::new(false), next_channel_id: AtomicU64::new(0), @@ -378,8 +385,8 @@ impl RuntimeProxy { &self, runtime_config: crate::RuntimeConfiguration, ) -> Result { - let module_name = self.runtime.configuration.entry_module.clone(); - let entrypoint = self.runtime.configuration.entrypoint.clone(); + let module_name = self.runtime.application_configuration.entry_module.clone(); + let entrypoint = self.runtime.application_configuration.entrypoint.clone(); self.metrics_data() .runtime_metrics .runtime_health_check @@ -1249,7 +1256,7 @@ impl Runtime { let reader = self.abi_to_read_half(node_id, initial_handle)?; let config = self - .configuration + .application_configuration .nodes .get(config_name) .ok_or(OakStatus::ErrInvalidArgs)?; @@ -1279,7 +1286,12 @@ impl Runtime { debug!("{:?}: create node instance {:?}", node_id, new_node_id); // This only creates a Node instance, but does not start it. let instance = config - .create_node(&new_node_name, config_name, entrypoint.to_owned()) + .create_node( + &new_node_name, + config_name, + entrypoint.to_owned(), + &self.grpc_configuration, + ) .ok_or(OakStatus::ErrInvalidArgs)?; debug!("{:?}: start node instance {:?}", node_id, new_node_id);