Skip to content

Commit

Permalink
Introduce config map format and parsing logic (#1004)
Browse files Browse the repository at this point in the history
This will be used to pass configuration files and secrets to Oak
Applications, by specifying them via command line flags to the
`oak_loader` binary.

Also switch the top-level `oak_loader` error handling to the `anyhow`
crate.

Ref #689
  • Loading branch information
tiziano88 authored May 19, 2020
1 parent 0de07ac commit f19bebb
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cargo/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ package(default_visibility = ["//visibility:public"])
licenses([
"notice" # See individual crates for specific licenses
])
alias(
name = "anyhow",
actual = "@raze__anyhow__1_0_28//:anyhow",
)
alias(
name = "byteorder",
actual = "@raze__byteorder__1_3_4//:byteorder",
Expand Down
1 change: 1 addition & 0 deletions cargo/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cargo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version = "0.0.0"
path = "fake_lib.rs"

[dependencies]
anyhow = "*"
byteorder = "*"
bytes = "*"
futures = "*"
Expand Down
11 changes: 11 additions & 0 deletions oak/proto/application.proto
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,14 @@ message RoughtimeServer {
uint32 port = 3;
string public_key_base64 = 4;
}

// A serialized list of key-value pairs that are specified as command line flags to the Oak Loader
// binary, and are made available to the initial Node of the running Oak Application.
//
// Keys are human readable strings and usually correspond to file names.
//
// Values are raw binary blobs and usually correspond to file contents, which must be interpreted by
// the running Oak Application.
message ConfigMap {
map<string, bytes> items = 1;
}
1 change: 1 addition & 0 deletions oak/server/rust/oak_loader/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ rust_binary(
],
edition = "2018",
deps = [
"//cargo:anyhow",
"//cargo:log",
"//cargo:prost",
"//cargo:signal_hook",
Expand Down
4 changes: 4 additions & 0 deletions oak/server/rust/oak_loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ oak_debug = []
default = ["oak_debug"]

[dependencies]
anyhow = "*"
log = "*"
oak_runtime = "=0.1.0"
prost = "*"
signal-hook = "*"
simple_logger = "*"
structopt = "*"

[dev-dependencies]
maplit = "*"
77 changes: 68 additions & 9 deletions oak/server/rust/oak_loader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@
//! --grpc-tls-certificate=<CERTIFICATE_PATH> \
//! --root-tls-certificate=<CERTIFICATE_PATH>

use log::info;
use anyhow::anyhow;
use core::str::FromStr;
use log::{debug, info};
use oak_runtime::{configure_and_run, proto::oak::application::ApplicationConfiguration};
use prost::Message;
use std::{
collections::HashMap,
fs::{read_to_string, File},
io::Read,
sync::{
Expand All @@ -37,11 +40,15 @@ use std::{
};
use structopt::StructOpt;

use oak_runtime::proto::oak::application::node_configuration::ConfigType::{
GrpcClientConfig, GrpcServerConfig,
#[cfg(test)]
mod tests;

use oak_runtime::proto::oak::application::{
node_configuration::ConfigType::{GrpcClientConfig, GrpcServerConfig},
ConfigMap,
};

#[derive(StructOpt, Clone)]
#[derive(StructOpt, Clone, Debug)]
#[structopt(about = "Oak Loader")]
pub struct Opt {
#[structopt(long, help = "Application configuration file.")]
Expand Down Expand Up @@ -70,24 +77,76 @@ pub struct Opt {
introspect_port: u16,
#[structopt(long, help = "Starts the Runtime without an introspection server.")]
no_introspect: bool,
#[structopt(
long,
help = "Configuration files to expose to the Oak Application, each in key=filename format."
)]
config_files: Vec<ConfigEntry>,
}

fn read_file(filename: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
/// A specification of a configuration entry as human readable key and a path to a file whose
/// contents constitutes the actual value.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConfigEntry {
key: String,
filename: String,
}

impl FromStr for ConfigEntry {
type Err = anyhow::Error;
fn from_str(v: &str) -> Result<Self, Self::Err> {
let parts = v.split('=').collect::<Vec<_>>();
if parts.len() != 2 {
return Err(anyhow!("could not parse config entry: {}", v));
}
Ok(ConfigEntry {
key: parts[0].to_string(),
filename: parts[1].to_string(),
})
}
}

fn read_file(filename: &str) -> anyhow::Result<Vec<u8>> {
let mut file = File::open(filename)
.map_err(|error| format!("Failed to open file <{}>: {:?}", filename, error))?;
.map_err(|error| anyhow!("Failed to open file <{}>: {:?}", filename, error))?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)
.map_err(|error| format!("Failed to read file <{}>: {:?}", filename, error))?;
.map_err(|error| anyhow!("Failed to read file <{}>: {:?}", filename, error))?;
Ok(buffer)
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
fn parse_config_files(config_entries: &[ConfigEntry]) -> anyhow::Result<HashMap<String, Vec<u8>>> {
let mut file_map = HashMap::new();
for config_entry in config_entries {
if file_map.contains_key(&config_entry.key) {
return Err(anyhow!("duplicate config entry key: {}", config_entry.key));
}
let file_content = read_file(&config_entry.filename)?;
file_map.insert(config_entry.key.to_string(), file_content);
}
Ok(file_map)
}

pub fn parse_config_map(config_files: &[ConfigEntry]) -> anyhow::Result<ConfigMap> {
Ok(ConfigMap {
items: parse_config_files(config_files)?,
})
}

fn main() -> anyhow::Result<()> {
if cfg!(feature = "oak_debug") {
simple_logger::init_by_env();
} else {
eprintln!("No debugging output configured at build time");
}
let opt = Opt::from_args();
debug!("parsed opts: {:?}", opt);

let config_map = parse_config_map(&opt.config_files)?;
// We only log the keys here, since the values may be secret.
debug!("parsed config map entries: {:?}", config_map.items.keys());
// TODO(#689): Pass the `config_map` object to the Runtime instance, and make it available to
// the running Oak Application.

// Load application configuration.
let app_config_data = read_file(&opt.application)?;
Expand Down Expand Up @@ -129,7 +188,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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| format!("status {:?}", status))?;
.map_err(|status| anyhow!("status {:?}", status))?;
info!(
"initial node {:?} with write handle {:?}",
runtime.node_id, initial_handle
Expand Down
133 changes: 133 additions & 0 deletions oak/server/rust/oak_loader/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//
// Copyright 2020 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

use super::*;
use maplit::hashmap;

#[test]
fn parse_config_entry_ok() {
assert_eq!(
ConfigEntry {
key: "foo".to_string(),
filename: "/dev/null".to_string()
},
"foo=/dev/null"
.parse::<ConfigEntry>()
.expect("could not parse config entry")
);
}

#[test]
fn parse_config_entry_multiple_equals_err() {
assert_eq!(
false,
"foo=/dev/null=/foo/bar".parse::<ConfigEntry>().is_ok()
);
}

#[test]
fn parse_config_entry_missing_equals_err() {
assert_eq!(false, "/dev/null".parse::<ConfigEntry>().is_ok());
}

#[test]
fn parse_config_map_multiple_ok() {
let result = parse_config_map(&[
ConfigEntry {
key: "foo".to_string(),
filename: "/dev/null".to_string(),
},
ConfigEntry {
key: "authors".to_string(),
filename: "../../../../AUTHORS".to_string(),
},
]);
assert_eq!(
ConfigMap {
items: hashmap! {
"foo".to_string() => vec![],
"authors".to_string() => b"# This is the list of Project Oak authors for copyright purposes.
#
# This does not necessarily list everyone who has contributed code, since in
# some cases, their employer may be the copyright holder. To see the full list
# of contributors, see the revision history in source control.
Google LLC
".iter().cloned().collect(),
},
},
result.expect("could not parse config")
);
}

#[test]
fn parse_config_map_dev_null_ok() {
let result = parse_config_map(&[ConfigEntry {
key: "foo".to_string(),
filename: "/dev/null".to_string(),
}]);
assert_eq!(
ConfigMap {
items: hashmap! {
"foo".to_string() => vec![],
},
},
result.expect("could not parse config")
);
}

#[test]
fn parse_config_map_duplicate_key_err() {
let result = parse_config_map(&[
ConfigEntry {
key: "foo".to_string(),
filename: "/dev/null".to_string(),
},
ConfigEntry {
key: "foo".to_string(),
filename: "/dev/null".to_string(),
},
]);
assert_eq!(false, result.is_ok());
}

#[test]
fn parse_config_map_non_existing_file_err() {
let result = parse_config_map(&[ConfigEntry {
key: "foo".to_string(),
filename: "/non-existing-file".to_string(),
}]);
assert_eq!(false, result.is_ok());
}

#[test]
fn parse_config_map_directory_err() {
let result = parse_config_map(&[ConfigEntry {
key: "foo".to_string(),
// Directory.
filename: "/etc".to_string(),
}]);
assert_eq!(false, result.is_ok());
}

#[test]
fn parse_config_map_no_permission_err() {
let result = parse_config_map(&[ConfigEntry {
key: "foo".to_string(),
// File only readable by the root user.
filename: "/etc/sudoers".to_string(),
}]);
assert_eq!(false, result.is_ok());
}
12 changes: 12 additions & 0 deletions oak/server/rust/oak_runtime/src/proto/oak.application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,15 @@ pub struct RoughtimeServer {
#[prost(string, tag="4")]
pub public_key_base64: std::string::String,
}
/// A serialized list of key-value pairs that are specified as command line flags to the Oak Loader
/// binary, and are made available to the initial Node of the running Oak Application.
///
/// Keys are human readable strings and usually correspond to file names.
///
/// Values are raw binary blobs and usually correspond to file contents, which must be interpreted by
/// the running Oak Application.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ConfigMap {
#[prost(map="string, bytes", tag="1")]
pub items: ::std::collections::HashMap<std::string::String, std::vec::Vec<u8>>,
}

0 comments on commit f19bebb

Please sign in to comment.