Giter VIP home page Giter VIP logo

oak's Introduction

Project Oak Logo

Build Status Docs

Project Oak leverages Trusted Execution Environment (TEE) to provide security and a radical level of transparency around how data is used and by whom, even in a highly distributed system. Through a combination of strong isolation from the host, end-to-end encryption, Remote Attestation, open sourcing and Transparent Release, Oak allows developers to make verifiable (or falsifiable) claims about server-side computation that are publicly inspectable.

The following is a simplified architectural diagram.

architectural_overview

Terminology

  • Trusted Computing: The practice of protecting in-use data by performing computing in a confidential environment built on trusted hardware, and additionally, offering tools to allow the users and/or their delegate to inspect and verify the code (and associated workload) being run in that environment. People also often use the term “confidential computing” in similar ways.
  • Trusted Execution Environment (TEE): An environment for executing code in a secure area of a processor. It helps code and data loaded inside it to be protected with respect to confidentiality and integrity. Examples of TEE secure processors are AMD SEV-SNP and Intel SGX and TDX. Intel SGX is a process-based TEE, while AMD SEV-SNP and Intel TDX are VM-based TEEs. Oak uses VM-based TEEs only.
  • Remote attestation: A process to verify the identity and integrity of the trusted computing environment and application running in that environment. Remote attestation plays a major role in the security guarantees of TEE. It ensures that the application is running inside a genuine TEE by proving its identity to the client. By using the hardware-based TEE, the root of trust relies on hardware built-in crypto keys signed by the hardware vendor. This allows the client to not place the trust solely in the service provider running the server application, but also in the hardware vendor.
  • Transparent release: The processes and tools used to either reproduce the Oak build externally, or using a trusted builder (e.g. slsa-github-generator) to generate and publish binaries and the non-forgeable metadata about a released binary. Along with remote attestation, this release process can be used to provide evidence of the integrity and trustworthiness of the runtime in the TEE.

How does Oak work?

In present computing platforms (including virtualized, and cloud platforms), data may be encrypted at rest and in transit, but they are exposed to any part of the system that needs to process them. Even if the application is securely designed and data is encrypted, the operating system kernel (and any component with privileged access to the machine that handles the data) has unrestricted access to the machine hardware resources, and can leverage that to bypass any security mechanism on the machine itself and extract secret keys and data.

Oak is a trusted computing server platform designed to run in VM-based Trusted Execution Environment (TEE). An application running on Oak, in the TEE hardware, can process private data confidentially, without the service provider hosting the server from accessing the data being processed. Oak uses Remote Attestation and Transparent Release to permit a client to verify workload identity and trace it to public source code. This enables the application running on Oak to make external verifiable claims of privacy, security, integrity, information flow, and functional correctness.

In an Oak environment, a client (e.g. Android, Chrome) connects to an Oak server, and establishes an end-to-end encrypted channel with an instance of a Enclave Application, which is remotely attested.

Oak libraries can be composed in different ways to set up a trusted server. One configuration is the split architecture

  • Host Application: a binary that runs on the normal (untrusted) server environment that acts as the front-end for the Enclave Application, and exposes a gRPC endpoint which is externally reachable by the client. This binary only ever sees encrypted data, its main job is to create and maintain an instance of the Enclave Application in the TEE, and forward the encrypted data to and from the Enclave Application. This binary does not need to be externally buildable / reproducible and does not need to be trusted by the client.
  • Enclave Application: a binary built from open source code, running on the Oak platform. It runs inside the confidential VM on the TEE hardware. The Enclave Application handles user data in unencrypted form by decrypting user requests and encrypting responses. This is the binary that is associated with externally verifiable claims.

At startup, the Enclave Application generates an ephemeral key pair (or, alternatively, obtains a persistent key pair from a separate Key Provisioning Service, based on the use case), and binds its public key to a remote attestation report generated by the TEE (e.g. AMD SEV-SNP or Intel TDX). This also includes a measurement (i.e. hash) of the Enclave Application running in the TEE. All this information is bound together in a statement signed by a secret key that is fused in the hardware by the TEE manufacturer (e.g. AMD, Intel), and that the service provider does not have access to. The corresponding public key is published externally by the manufacturer.

A client can verify the identity of the Oak server and its properties before sending any data to it, by checking the signature generated by the TEE (controlled by the TEE hardware manufacturer), and encrypt data so that it can only be decrypted by the attested instance of the Enclave Application.

This "trusted" architecture enables a client to be convinced of the claims made by the Enclave Application, without having to trust the service provider hosting the Enclave Application. Furthermore, the service provider would not be able to swap the Enclave Application binary with a different (potentially malicious) one without being detected. If the service provider were to introduce a backdoor in the Enclave Application, it would have to sign the binary and publish it in a verifiable log via the Transparent Release process, which would make the backdoor irreversibly visible to external observers.

Oak provides two flavors of VM-based Enclave Applications:

  • Oak Restricted Kernel: The restricted kernel is a very minimal microkernel that only supports a single CPU and a single application. It also places very severe restrictions on what the application can do by only exposing a very limited set of syscalls to the application. Restricted kernel applications are suitable for cases where review of the entire trusted code base inside the VM is critical, and the limited features and reduced performance is an acceptable trade-off.
  • Oak Containers: The Enclave Application is provided as part of an OCI runtime bundle. This includes a full Linux kernel and a Linux distribution with a container runtime engine inside the Enclave VM. Using the OCI runtime bundle as a compatibility layer provides a more familiar development experience and more flexibility. Oak Containers also supports multiple CPUs in the VM and is therefore suitable for cases with high performance requirements. The draw-back of this approach is that the size of the entire trusted code base running in the enclave VM is significantly increased, and it is not feasible for a single reviewer to review the code for the entire Linux kernel and Linux distribution. The trust model for his approach relies on the security of the broader Linux ecosystem. The additional features and flexibility that the Linux kernel provides to the application also makes it more difficult to reason about the behavior of an application, which in turn makes the review process more complicated.

Threat Model

We acknowledge that perfect security is impossible. Here we describe the primary threats the Oak architecture is designed to defend against. We also list threats that are currently out of scope.

Project Oak’s primary objective is to protect the user data from unauthorized access from the service provider who host the service in their data center, and offer proof to the users, or their delegate, that their data can only be used in accordance with the pre-approved usage policies. Below is a list of actors and potential adversaries.

Untrusted

  • most hardware (memory, disk, motherboard, network card, external devices)
  • Host Operating System (kernel, drivers, libraries, applications)
  • Hypervisor / VMM

The service provider may intend to access and exfiltrate users’ private data. They own the server machines in the data center, therefore have server root access and are capable of modifying the host machine OS, drivers, hypervisor, etc. They can also inspect network traffic that contains users’ data.

In the Oak architecture, we treat the host hardware and the host OS operated by the service provider as untrusted. The data that need to be protected need to be encrypted when stored in the host OS’s memory system, with the decryption keys managed by the TEE, not accessible by the service provider hosting the hardware machines.

The Oak architecture allows applications to make stronger claims by reducing the need to trust the service operators.

Trusted-but-verifiable

  • Oak trusted platform
  • Enclave application

Both Oak platform, and the enclave application that runs on that platform, run inside the TEE. They have access to unencrypted data. Oak establishes trust for these components by open sourcing them, and enables external inspection and verification via Transparent Release.

Trusted

  • Hardware TEE manufacturer (e.g. AMD, Intel)

The hardware TEE is responsible for memory encryption and decryption, and generating the remote attestation report, etc. Therefore TEEs, and their manufacturers, are considered to be the trusted party by the Oak runtime.

Out of scope

We consider side channels to be out of scope. While we acknowledge that most existing TEEs have compromises and may be vulnerable to various kinds of attacks (and therefore we do need to defend against side channels) we leave the resolution to the respective TEE Manufacturers and other researchers.

We consider attacks that require physical access to the server to be out of scope. They are expensive, impractical, therefore not scalable. Also, none of the current Trusted Execution platforms provides full protection against physical attacks. We rely on service provider's physical datacenter security to restrict physical access to the servers.

We consider client-side security and protection of user data to be out of scope. The Oak runtime is designed for server-side TEE.

Getting involved

We welcome contributors! To join our community, we recommend joining the mailing list.

oak's People

Contributors

aalmos avatar alwabel1 avatar andrewkvuong avatar andrisaar avatar benlaurie avatar bgogul avatar blaxill avatar bmclarnon avatar conradgrobler avatar daviddrysdale avatar dependabot[bot] avatar dingelish avatar dzmitry-huba avatar ernoc avatar fattaneh88 avatar ianull avatar ipetr0v avatar jadephilipoom avatar jblebrun avatar jul-sh avatar k-naliuka avatar mariaschett avatar michael-kernel-sanders avatar msilezin avatar pmcgrath17 avatar rbehjati avatar thmsbinder avatar tiziano88 avatar waywardgeek avatar wildarch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oak's Issues

Set up Continuous Integration

This may end up being more complicated than expected because of the additional dependencies needed by Asylo that need to be installed on the system or in a docker image in which tests are run.

C++ example module not building on Travis

Looking inside a recent Travis build of master, the C++ example module is failing to build:

ERROR: /opt/my-project/examples/hello_world/module/cpp/BUILD:17:1: C++ compilation of rule '//examples/hello_world/module/cpp:hello_world.wasm' failed (Exit 127) clang.sh failed: error executing command toolchain/clang.sh -MD -MF bazel-out/wasm32-fastbuild/bin/examples/hello_world/module/cpp/_objs/hello_world.wasm/hello_world.d ... (remaining 20 argument(s) skipped)
Use --sandbox_debug to see verbose messages from the sandbox
[4 / 5] checking cached actions
/bin/clang -MD -MF bazel-out/wasm32-fastbuild/bin/examples/hello_world/module/cpp/_objs/hello_world.wasm/hello_world.d -frandom-seed=bazel-out/wasm32-fastbuild/bin/examples/hello_world/module/cpp/_objs/hello_world.wasm/hello_world.o -iquote . -iquote bazel-out/wasm32-fastbuild/bin -iquote external/bazel_tools -iquote bazel-out/wasm32-fastbuild/bin/external/bazel_tools -ffreestanding -isystem external/clang_llvm/lib/clang/8.0.0/include -isystem external/clang_llvm/include/c++/v1/ --target=wasm32-unknown-unknown -nostdlib -c examples/hello_world/module/cpp/hello_world.cc -o bazel-out/wasm32-fastbuild/bin/examples/hello_world/module/cpp/_objs/hello_world.wasm/hello_world.o
external/clang_llvm/bin/clang: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory
[4 / 5] checking cached actions
ples/hello_world/module/cpp:hello_world.wasm failed to build
[4 / 5] checking cached actions
mand lines of failed build steps.
[4 / 5] checking cached actions
INFO: Elapsed time: 45.133s, Critical Path: 0.42s
[4 / 5] checking cached actions
INFO: 0 processes.
[4 / 5] checking cached actions
FAILED: Build did NOT complete successfully
FAILED: Build did NOT complete successfully

It would be good if:

  • The C++ module built correctly on Travis.
  • Failures to build the C++ module on Travis got reported as failed builds.
  • The C++ module got run as part of the EXAMPLES_TEST=true Travis run.

Implement Oak remote attestation over gRPC

Each Oak Node should produce a remote attestation that confirms to the client the policy configuration and the Oak Module that are bound to that Oak Node. This should happen after the lower-level attestation (via Asylo) is verified, but before any actual data is transferred between client and server.

oak/oak/server/oak_node.cc

Lines 349 to 353 in c9c13c3

::grpc::Status OakNode::GetAttestation(::grpc::ServerContext* context,
const ::oak::GetAttestationRequest* request,
::oak::GetAttestationResponse* response) {
return ::grpc::Status(::grpc::StatusCode::UNIMPLEMENTED, "TODO");
}

stand-alone Oak VM

We should create new versions of the Oak VM and Oak Manager that do not depend on Asylo for isolation and remote attestation. The entry point should be a separate implementation of Oak Manager that implements the same gRPC interface as the current one:

oak/oak/proto/manager.proto

Lines 136 to 143 in 7481fe2

service Manager {
// Request the creation of a new Oak Application with the specified configuration.
//
// After the Oak Node is created, the client should connect to the returned
// endpoint via gRPC and perform a direct attestation against the Node itself,
// to verify that its configuration corresponds to what the client expects.
rpc CreateApplication(CreateApplicationRequest) returns (CreateApplicationResponse);
}

but instead of creating enclaves via Asylo, it just runs new Oak Nodes as separate processes (or potentially V8 isolates, in the future).

For now, each Oak Node will still run with a stand-alone gRPC server stack; in the future we should also refactor this so that we can run Oak Nodes with minimal support from the OS (ideally only communication channels), and the networking stack would live outside of the Oak Node itself. Obviously any data going in or out of the Node would still have to be encrypted by the Node itself.

API for creating static attestations

It would be useful to have a way for Oak Modules to produce a stand-alone attestation (e.g. a signature on top of user-provided data) rooted in the underlying enclave.

Some use cases that this could be useful for:

  • attesting to the output of a long / expensive computation (e.g. compute 1B digits of pi)
  • attesting to the artifact produced by a compiler given a specified source
  • attesting to the verification / summary of a log performed by a trusted aggregation function (e.g. verifying a Trillian log)

This should probably be a native WebAssembly host function implemented by the Oak VM and exposed to the Oak Modules, and also correspondingly wrapped in more ergonomic API in Rust or future SDKs.

New example: rustfmt

Create a new example that runs the rustfmt formatter in an Oak Node, and a sample client that sends a string containing a snippet of Rust code and gets back its formatted version.

Implement Rust logging facade

We should have a way for an Oak Module to initialize a log connected to an output channel.

Note when I tried this in the past it resulted in some odd behaviour because the logger may perform multiple calls to write for a single log message (e.g. when interpolating arguments). Perhaps we should wrap the output channel with a LineWriter or something like that.

Use `dyn` when dispatching on traits in generated gRPC code

warning: trait objects without an explicit `dyn` are deprecated
  --> rustfmt/module/rust/src/proto/hello_world_grpc.rs:38:28
   |
38 | pub fn dispatch(node: &mut HelloWorldNode, grpc_method_name: &str, grpc_pair: &mut oak::ChannelPair) {
   |                            ^^^^^^^^^^^^^^ help: use `dyn`: `dyn HelloWorldNode`
   |
   = note: `#[warn(bare_trait_objects)]` on by default

Embed Wasm engine using wasm-c-api

wasm-c-api

Provide[s] a "black box" API for embedding a Wasm engine in other C/C++ applications.

There should be prototypes of V8 implementing this interface, and hopefully also other Wasm engines; not sure if wabt, which we are currently using, also implements this.

This should also help with #74 (in fact I would say this is a prerequisite).

cc @hansman

Set docker UID

By default, Docker container has uid 0 (root). This is not only a security risk, but annoying because any files output by the container will be owned by root.

Migrate to Chrome V8 from wabt

V8 is Chrome's Javascript and WebAssembly engine. It is well tested in both server and client contexts, and provides multi-tenancy in form of isolates (see https://www.infoq.com/presentations/cloudflare-v8).

The plan is to replace wabt with V8, and have the Oak Manager create an isolate per Oak Node within the same Oak VM (as opposed to the current model in which each Oak Node runs a full independent Oak VM).

On server, a single Oak VM containing a single V8 instance will run within Asylo, relying on the underlying remote attestation mechanism to derive a key for the entire Oak VM; the Oak VM will then derive further keys (one per isolate) that it will use for attestation (both local and remote) between Oak Nodes.

On client, a hypervisor will run the Oak VM in a similar way, and provide it with the necessary key for attestation, which the Oak VM will then use in the same way as for the server case.

In both cases, the Oak VM as a whole is isolated from the host via the underlying enclave technology (Asylo or hypervisor), but isolation between Oak Nodes is only enforced by V8 itself.

Move OakScheduler class to separate file

class OakScheduler final : public ::oak::Scheduler::Service {
public:
OakScheduler() : Service(), node_id_(0) { InitializeEnclaveManager(); }
::grpc::Status CreateNode(::grpc::ServerContext *context, const ::oak::CreateNodeRequest *request,
::oak::CreateNodeResponse *response) override {
std::string node_id = NewNodeId();
CreateEnclave(node_id, request->module());
oak::InitializeOutput out = GetEnclaveOutput(node_id);
response->set_port(out.port());
response->set_node_id(node_id);
return ::grpc::Status::OK;
}
private:
void InitializeEnclaveManager() {
LOG(INFO) << "Initializing enclave manager";
asylo::EnclaveManager::Configure(asylo::EnclaveManagerOptions());
auto manager_result = asylo::EnclaveManager::Instance();
if (!manager_result.ok()) {
LOG(QFATAL) << "Could not initialize enclave manager: " << manager_result.status();
}
enclave_manager_ = manager_result.ValueOrDie();
LOG(INFO) << "Enclave manager initialized";
LOG(INFO) << "Loading enclave code from " << FLAGS_enclave_path;
enclave_loader_ = absl::make_unique<asylo::SimLoader>(FLAGS_enclave_path, /*debug=*/true);
}
void CreateEnclave(const std::string &node_id, const std::string &module) {
LOG(INFO) << "Creating enclave";
asylo::EnclaveConfig config;
oak::InitializeInput *initialize_input = config.MutableExtension(oak::initialize_input);
initialize_input->set_node_id(node_id);
initialize_input->set_module(module);
asylo::Status status = enclave_manager_->LoadEnclave(node_id, *enclave_loader_, config);
if (!status.ok()) {
LOG(QFATAL) << "Could not load enclave " << FLAGS_enclave_path << ": " << status;
}
LOG(INFO) << "Enclave created";
}
oak::InitializeOutput GetEnclaveOutput(const std::string &node_id) {
LOG(INFO) << "Initializing enclave";
asylo::EnclaveClient *client = enclave_manager_->GetClient(node_id);
asylo::EnclaveInput input;
asylo::EnclaveOutput output;
asylo::Status status = client->EnterAndRun(input, &output);
if (!status.ok()) {
LOG(QFATAL) << "EnterAndRun failed: " << status;
}
LOG(INFO) << "Enclave initialized";
return output.GetExtension(oak::initialize_output);
}
std::string NewNodeId() {
// TODO: Generate UUID.
std::stringstream id_str;
id_str << node_id_;
node_id_ += 1;
return id_str.str();
}
void DestroyEnclave(const std::string &node_id) {
LOG(INFO) << "Destroying enclave";
asylo::EnclaveClient *client = enclave_manager_->GetClient(node_id);
asylo::EnclaveFinal final_input;
asylo::Status status = enclave_manager_->DestroyEnclave(client, final_input);
if (!status.ok()) {
LOG(QFATAL) << "Destroy " << FLAGS_enclave_path << " failed: " << status;
}
LOG(INFO) << "Enclave destroyed";
}
asylo::EnclaveManager *enclave_manager_;
std::unique_ptr<asylo::SimLoader> enclave_loader_;
uint64_t node_id_;
};

Allocate new port for each Oak Node instance

Currently the Oak Scheduler creates new Oak Node instances all listening on the same port (see

builder.AddListeningPort("[::]:30000", credentials_, &port_);
), which means that when a client attempts to connect to it and there are multiple Oak Nodes listening there, the client will be connected to an arbitrary Oak Node among those running there.

We should instead let each Oak Node pick a free port on the host, and return that as part of the scheduling response.

Note the main reason why we have not done this yet is because we run the Oak Scheduler in Docker, and we need to list in advance all the ports to expose (see

--publish=30000:30000 \
)

Oak Node lifetime management

We should support a way to manage lifetime of Oak Nodes. For now this may be an RPC on Oak Manager that allows the caller to shut down a running Application.

oak/oak/proto/manager.proto

Lines 135 to 143 in fb64cf6

// Untrusted service in charge of creating Oak Applications on demand.
service Manager {
// Request the creation of a new Oak Application with the specified configuration.
//
// After the Oak Node is created, the client should connect to the returned
// endpoint via gRPC and perform a direct attestation against the Node itself,
// to verify that its configuration corresponds to what the client expects.
rpc CreateApplication(CreateApplicationRequest) returns (CreateApplicationResponse);
}

Improve mechanism for passing data from runtime to the Oak Module instance

Currently we use a combination of an owned vector and a cursor in the Oak Node to allow the Oak Module to consume data from the runtime.

std::unique_ptr<std::vector<char>> request_data_;
// Cursor keeping track of how many bytes of request_data_ have been consumed by the Oak Module
// during the current invocation.
uint32_t request_data_cursor_;

For instance, when a gRPC call reaches the Oak Node, the oak_handle_grpc_call entry point of the module is invoked, with no arguments. The handler then needs to invoke the read_method_name and read host function calls in order to request the appropriate data (gRPC method name and payload, respectively) to be returned. This currently follows an approach similar to that of the read syscall (http://man7.org/linux/man-pages/man2/read.2.html) by which the module allocates a buffer of arbitrary size, passes its pointer and size to the runtime, and the runtime fills in the relevant data and returns the number of bytes written; the module then asks for more data, potentially having to reallocate the underlying buffer each time.

oak/oak/server/oak_node.cc

Lines 241 to 266 in c9c13c3

::wabt::interp::HostFunc::Callback OakNode::OakRead(wabt::interp::Environment* env) {
return [this, env](const wabt::interp::HostFunc* func, const wabt::interp::FuncSignature* sig,
const wabt::interp::TypedValues& args, wabt::interp::TypedValues& results) {
LOG(INFO) << "Called host function: " << func->module_name << "." << func->field_name;
for (auto const& arg : args) {
LOG(INFO) << "Arg: " << wabt::interp::TypedValueToString(arg);
}
// TODO: Synchronise this method.
uint32_t p = args[0].get_i32();
uint32_t len = args[1].get_i32();
uint32_t start = request_data_cursor_;
uint32_t end = start + len;
if (end > request_data_->size()) {
end = request_data_->size();
}
WriteMemory(env, p, request_data_->cbegin() + start, request_data_->cbegin() + end);
results[0].set_i32(end - start);
request_data_cursor_ = end;
return wabt::interp::Result::Ok;
};
}

Note that we cannot just copy the memory from the runtime into the Wasm memory area, because the memory there is managed by the runtime (e.g. the Rust memory allocator), and therefore we cannot allocate it "from the outside" or it may conflict with it.

An initial improvement could be to remove the need for the vector and cursor (https://team.git.corp.google.com/project-oak-team/oak/+/refs/heads/master/oak/server/oak_node.h#61) and replace both with a single forward-only iterator, in order to avoid both copying the data in the Oak Module itself, and also storing explicit state to keep track of what the module has consumed so far; with an iterator, the state would be encapsulated in the iterator itself.

Unify error handling

We currently use a combination of the following approaches for error handling:

  • return ::grpc::Status
  • return ::asylo::StatusOr
  • log errors (at inconsistent levels) and discard them

For gRPC methods it makes sense to keep returning ::grpc::Status, but for general methods that may fail we should probably converge on something like StatusOr; unfortunately this is not available in absl yet, but I think we could piggyback on the asylo implementation for now, and switch to the absl version once it becomes available.

@michael-kernel-sanders WDYT?

Implement basic identity-based authorization

Currently each Oak Node allows clients to interact with it via gRPC based on just being able to connect to the port on which the Oak Node is listening, which means that anyone would be able to interact with it. Although the long-term story for policies is still being discussed, we will most definitely have to support some form of public key based authentication and authorization.

Asylo already supports cert identities as part of the gRPC channel credentials (see https://github.com/google/asylo/blob/master/asylo/identity/identity.proto#L40), so we should be able to obtain the credentials for any client connecting to the Oak Node and verify that they match to one of the expectations with which the Oak Node was initialized (by the Oak Scheduler). (cc @annasapek)

To start with we may just derive a single bit of information from this (authorized or not), but later on we should have roles, which will probably map to policies / capabilities (e.g. read, write, etc.)

Target //oak/server:oak_manager can't be built on its own

If you build only oak_manager with bazel build --config=enc-sim //oak/server:oak_manager you'll meet the following error:

C++ compilation of rule '@linux_sgx//:urts_sim' failed (Exit 1) x86_64-elf-g++ failed: error executing command external/com_google_asylo_toolchain/toolchain/bin/x86_64-elf-g++ -isystemasylo/platform/posix/include -isystemasylo/platform/system/include ... (remaining 86 argument(s) skipped)

Use --sandbox_debug to see verbose messages from the sandbox
In file included from external/linux_sgx/common/inc/internal/se_wrapper.h:40:0,
                from external/linux_sgx/psw/urts/linux/urts_internal.cpp:40:
external/linux_sgx/common/inc/internal/se_thread.h:43:10: fatal error: sys/syscall.h: No such file or directory
#include <sys/syscall.h>
         ^~~~~~~~~~~~~~~
compilation terminated.
Target //oak/server:oak_manager failed to build

When it'll come to writing tests IMO it'll be problematic.

Code generation for Oak Module gRPC interface

Currently each Oak Module needs to explicitly dispatch gRPC calls based on the incoming gRPC method name, then deserialise the incoming request according to the appropriate protobuf type, and eventually serialise the outgoing response. All of this is very mechanical and in fact is fully determined by the gRPC service definition in the source protobuf file. We should rely on code generation to automate and hide all of this complexity from module authors.

A possible approach would be to start from https://github.com/stepancheg/grpc-rust and modify it so that it generates the server trait / base struct. Note we do not need to generate clients, nor any of the transport layer logic.

See

// TODO: Generate this code via a macro or code generation (e.g. a protoc plugin).
match method_name {
"/oak.examples.running_average.RunningAverage/SubmitSample" => {
let mut in_stream = protobuf::CodedInputStream::new(request);
let mut req = proto::running_average::SubmitSampleRequest::new();
req.merge_from(&mut in_stream)
.expect("could not read request");
self.sum += req.value;
self.count += 1;
}
"/oak.examples.running_average.RunningAverage/GetAverage" => {
let mut res = proto::running_average::GetAverageResponse::new();
let mut out_stream = protobuf::CodedOutputStream::new(response);
res.average = self.sum / self.count;
res.write_to(&mut out_stream)
.expect("could not write response");
out_stream.flush().expect("could not flush");
}
_ => {
panic!("unknown method name");
}
};

Cache bazel artifacts in CI

I tried naively caching the whole bazel cache folder via the standard Travis mechanism, but it always fails to restore it, even if I increase the timeout. I think the cache is too large and there are too many small files for Travis to unpack it in a reasonable time.
Another option could be to use a Bazel remote cache, which has the advantage that we could also share the same cache when building locally.

Improve gRPC async generic service loop

Currently we process gRPC generic events in a "synchronous" way by just queuing each even and processing it immediately after in the same loop, but this is not idiomatic, and I think it also has the potential of breaking (e.g. if an additional event is inserted in between in some other way, the loop may get out of sync). We should change this to pass a reference to each task as part of the gRPC event tag, and then looking up the task when processing each event from the queue.

cc @vjpai who has kindly advised on this in the past.

// Consumes gRPC events from the completion queue in an infinite loop.
void CompletionQueueLoop() {
LOG(INFO) << "Starting gRPC completion queue loop";
int i = 0;
while (true) {
::grpc::GenericServerContext context;
::grpc::GenericServerAsyncReaderWriter stream(&context);
// We request a new event corresponding to a generic call. This will create an entry in the
// completion queue when a new call is available.
generic_service_.RequestCall(&context, &stream, completion_queue_.get(),
completion_queue_.get(), tag(i++));
// Wait for a generic call to arrive.
ProcessNextEvent();
// Read request data.
::grpc::ByteBuffer request;
stream.Read(&request, tag(i++));
// Process the event related to the read we just requested.
ProcessNextEvent();
::grpc::ByteBuffer response;
// Invoke the actual gRPC handler on the Oak Node.
::grpc::Status status = node_->HandleGrpcCall(&context, &request, &response);
if (!status.ok()) {
LOG(INFO) << "Failed: " << status.error_message();
}
::grpc::WriteOptions options;
// Write response data.
stream.WriteAndFinish(response, options, status, tag(i++));
// Process the event related to the write we just requested.
ProcessNextEvent();
}
}

Project can't be loaded with CLion

This is probably an issue with either (a) Bazel plugin for CLion or (b) Asylo, but I'm still debugging. The problem occurs when CLion tries to load target oak_enclave.so

Traceback (most recent call last):
	File "/home/ahomolya/.cache/bazel/_bazel_ahomolya/344f6b368a5432cb81bfc09bc231b915/external/com_google_asylo_toolchain/toolchain/BUILD", line 74
		@intellij_aspect//:intellij_info_bundled.bzl%intellij_info_aspect(...)
	File "/home/ahomolya/.cache/bazel/_bazel_ahomolya/344f6b368a5432cb81bfc09bc231b915/external/intellij_aspect/intellij_info_bundled.bzl", line 53, in _aspect_impl
		intellij_info_aspect_impl(target, ctx, semantics)
	File "/home/ahomolya/.cache/bazel/_bazel_ahomolya/344f6b368a5432cb81bfc09bc231b915/external/intellij_aspect/intellij_info_impl.bzl", line 816, in intellij_info_aspect_impl
		collect_c_toolchain_info(target, ctx, semantics, ide_info, <2 more arguments>)
	File "/home/ahomolya/.cache/bazel/_bazel_ahomolya/344f6b368a5432cb81bfc09bc231b915/external/intellij_aspect/intellij_info_impl.bzl", line 418, in collect_c_toolchain_info
		cc_common.get_memory_inefficient_command_line(feature_configuration = feature_..., <2 more arguments>)
Invalid toolchain configuration: Cannot find variable named 'output_file'.

Verify Oak remote attestation in Oak Node Client

NodeClient must accept an attestation assertion in its constructor; then when establishing a connection to a OakNode instance it must verify whether the remote attestation provided by the OakNode is allowed by the assertion.
For now let us assume that the underlying enclave-level assertion is already verified (most likely via an Asylo assertion at the channel level, which will attest whether the gRPC channel terminates inside an SGX enclave or similar), even though in practice that is not implemented yet.
See #6 for more context.

handle panics in extern function calls

We currently rely on well-defined panic behaviour from extern functions. This is actually undefined behaviour.

pub extern "C" fn oak_handle_grpc_call() {

oak/rust/oak/src/tests.rs

Lines 138 to 143 in 6de88af

#[test]
#[should_panic]
fn test_handle_grpc_call_no_node() {
reset_node();
oak_handle_grpc_call(); // no node: panic!
}

For some reason switching from stable to nightly Rust actually this manifests with a weird error:

error: process didn't exit successfully: `/usr/local/google/home/tzn/src/oak/rust/target/debug/deps/oak-092a7b70ef31d1ae` (signal: 4, SIGILL: illegal instruction)

Upgrade to asylo-3.4.0

As I see asylo-3.4.0 introduced some substantial changes in its build rules. I was able to build Oak with asylo-3.4.0 with some minor modifications, I'd advise doing so in master.

Implement Private Set Intersection (PSI) example

Some references:

To start with we can probably model this as a gRPC-based Oak Module with the following methods:

  • SubmitSet
  • RetrieveCurrentIntersection

Initial assumptions:

  • (unordered) sets of strings
  • small size (max ~1MB / set)
  • sets from different clients may have different size
  • side channels (e.g. size, completion time, memory access patterns) are out of scope

The workflow would involve the clients connecting to the Oak Node one after the other, submitting their set, then assuming there is a way of directly signalling to each other, they would each call the method to retrieve the current intersection, which should return the same value for all of them.

e.g.

  • client_0 sends the set {A, B, C, F}
  • client_1 sends the set {C, D, F, G}
  • client_0 retrieves current intersection, receiving {C, F}
  • client_1 retrieves current intersection, receiving {C, F}

Document build steps without Docker

Add documentation for building without Docker on selected platforms (Ubuntu?). I was able to build it on Arch, it should be possible to do so on arbitrary distros. Note that the project doesn't compile with Bazel version <0.2.3, it should be asserted too.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.