Skip to content

Commit

Permalink
Add support for mock SGX builds
Browse files Browse the repository at this point in the history
This makes it easier to test various features even when SGX hardware is
not available.
  • Loading branch information
kostko committed Apr 22, 2024
1 parent f261bc1 commit 22eaf6b
Show file tree
Hide file tree
Showing 29 changed files with 421 additions and 155 deletions.
31 changes: 31 additions & 0 deletions .buildkite/code.pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ steps:
- buildkite-agent artifact upload simple-keyvalue-upgrade
- buildkite-agent artifact upload simple-keymanager-upgrade
- buildkite-agent artifact upload simple-rofl

# Build for mock SGX.
- cd /workdir
- .buildkite/rust/build_runtime.sh tests/runtimes/simple-keymanager mocksgx
- .buildkite/rust/build_runtime.sh tests/runtimes/simple-keyvalue mocksgx
- cd /var/tmp/artifacts/default/release
- mv simple-keymanager simple-keymanager.mocksgx
- mv simple-keyvalue simple-keyvalue.mocksgx
- buildkite-agent artifact upload simple-keymanager.mocksgx
- buildkite-agent artifact upload simple-keyvalue.mocksgx
retry:
<<: *retry_agent_failure
plugins:
Expand Down Expand Up @@ -326,6 +336,27 @@ steps:
plugins:
<<: *docker_plugin

# E2E test jobs (mock SGX)
- label: E2E tests (mock SGX)
depends_on:
- "build-go"
- "build-rust-runtime-loader"
- "build-rust-runtimes"
command:
- trap 'buildkite-agent artifact upload "coverage-merged-e2e-*.txt;/tmp/e2e/**/*.log;/tmp/e2e/**/genesis.json;/tmp/e2e/**/runtime_genesis.json"' EXIT
- .buildkite/scripts/download_e2e_test_artifacts_mocksgx.sh
- .buildkite/scripts/test_e2e.sh --timeout 20m --scenario e2e/runtime/runtime-encryption
env:
OASIS_TEE_HARDWARE: intel-sgx
OASIS_UNSAFE_MOCK_SGX: "1"
OASIS_UNSAFE_SKIP_AVR_VERIFY: "1"
OASIS_E2E_COVERAGE: enable
TEST_BASE_DIR: /tmp
retry:
<<: *retry_agent_failure
plugins:
<<: *docker_plugin

####################################
# Rust coverage job.
####################################
Expand Down
32 changes: 23 additions & 9 deletions .buildkite/rust/build_runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ if [ ! -d $src_dir ]; then
fi
shift

###############
# Optional args
###############
mode=${1:-}

source .buildkite/rust/common.sh

#####################################################################
Expand All @@ -41,13 +46,22 @@ fi
# Run the build
###############
pushd $src_dir
# Build non-SGX runtime. Checking KM policy requires SGX, disable it.
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/default" OASIS_UNSAFE_SKIP_KM_POLICY="1" cargo build --release --locked

# Build SGX runtime.
export CFLAGS_x86_64_fortanix_unknown_sgx="-isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening"
export CC_x86_64_fortanix_unknown_sgx=clang-11
unset OASIS_UNSAFE_SKIP_KM_POLICY
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/sgx" cargo build --release --locked --target x86_64-fortanix-unknown-sgx
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/sgx" cargo elf2sgxs --release
case "${mode}" in
mocksgx)
# Mock SGX only.
unset OASIS_UNSAFE_SKIP_KM_POLICY
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/default" cargo build --features debug-mock-sgx --release --locked
;;
*)
# Build non-SGX runtime. Checking KM policy requires SGX, disable it.
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/default" OASIS_UNSAFE_SKIP_KM_POLICY="1" cargo build --release --locked

# Build SGX runtime.
export CFLAGS_x86_64_fortanix_unknown_sgx="-isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening"
export CC_x86_64_fortanix_unknown_sgx=clang-11
unset OASIS_UNSAFE_SKIP_KM_POLICY
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/sgx" cargo build --release --locked --target x86_64-fortanix-unknown-sgx
CARGO_TARGET_DIR="${CARGO_TARGET_DIR}/sgx" cargo elf2sgxs --release
;;
esac
popd
35 changes: 35 additions & 0 deletions .buildkite/scripts/download_e2e_test_artifacts_mocksgx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#! /bin/bash

###############################################
# Download common E2E build artifacts and make
# sure they are in the correct directories for
# E2E tests to run, etc, etc.
###############################################

# Helpful tips on writing build scripts:
# https://buildkite.com/docs/pipelines/writing-build-scripts
set -euxo pipefail

source .buildkite/scripts/common.sh

# Randomize beginning of downloads to increase hits in CI pipeline cache
sleep $((RANDOM % 5))

# Oasis node, test runner and runtime loader.
download_artifact oasis-node go/oasis-node 755
download_artifact oasis-node.test go/oasis-node 755
download_artifact oasis-test-runner go/oasis-test-runner 755
download_artifact oasis-test-runner.test go/oasis-test-runner 755

# Runtime loader.
download_artifact oasis-core-runtime-loader target/default/release 755

# Simple key manager runtime.
download_artifact simple-keymanager.mocksgx target/default/release 755
mv target/default/release/simple-keymanager.mocksgx target/default/release/simple-keymanager
download_artifact simple-keymanager.sgxs target/sgx/x86_64-fortanix-unknown-sgx/release 755

# Test simple-keyvalue runtime.
download_artifact simple-keyvalue.mocksgx target/default/release 755
mv target/default/release/simple-keyvalue.mocksgx target/default/release/simple-keyvalue
download_artifact simple-keyvalue.sgxs target/sgx/x86_64-fortanix-unknown-sgx/release 755
4 changes: 4 additions & 0 deletions .changelog/5642.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add support for mock SGX builds

This makes it easier to test various features even when SGX hardware is
not available.
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ all: build
@$(ECHO) "$(CYAN)*** Everything built successfully!$(OFF)"

# Build.
build-targets := build-tools build-runtimes build-rust build-go
build-targets := build-tools build-rust build-runtimes build-go

build-tools:
@$(ECHO) "$(MAGENTA)*** Building Rust tools...$(OFF)"
Expand All @@ -24,7 +24,7 @@ build-runtimes:
$(ECHO) "$(MAGENTA)*** Building runtime: $$e...$(OFF)"; \
(cd $$e && \
CARGO_TARGET_DIR=$${CARGO_TARGET_ROOT}/sgx cargo build --release --target x86_64-fortanix-unknown-sgx && \
CARGO_TARGET_DIR=$${CARGO_TARGET_ROOT}/default cargo build --release && \
CARGO_TARGET_DIR=$${CARGO_TARGET_ROOT}/default cargo build --release $(OASIS_RUNTIME_NONSGX_FLAGS) && \
CARGO_TARGET_DIR=$${CARGO_TARGET_ROOT}/sgx cargo elf2sgxs --release \
) || exit 1; \
done
Expand Down
7 changes: 7 additions & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,10 @@ endif
# For more details, see:
# https://goreleaser.com/customization/build/#define-build-tag
export GORELEASER_CURRENT_TAG := $(RELEASE_TAG)

# If mock SGX is configured, define extra runtime build flags.
ifdef OASIS_UNSAFE_MOCK_SGX
OASIS_RUNTIME_NONSGX_FLAGS := --features debug-mock-sgx
else
OASIS_RUNTIME_NONSGX_FLAGS :=
endif
4 changes: 4 additions & 0 deletions go/oasis-test-runner/oasis/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/log"
"github.com/oasisprotocol/oasis-core/go/oasis-test-runner/oasis/cli"
roothash "github.com/oasisprotocol/oasis-core/go/roothash/api"
runtimeConfig "github.com/oasisprotocol/oasis-core/go/runtime/config"
scheduler "github.com/oasisprotocol/oasis-core/go/scheduler/api"
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
)
Expand Down Expand Up @@ -684,6 +685,9 @@ func (net *Network) startOasisNode(
if os.Getenv("OASIS_UNSAFE_LAX_AVR_VERIFY") != "" {
extraArgs = extraArgs.debugTCBLaxVerify()
}
if os.Getenv("OASIS_UNSAFE_MOCK_SGX") != "" {
cfg.Runtime.Environment = runtimeConfig.RuntimeEnvironmentSGXMock
}
} else {
baseArgs = append(baseArgs, "--"+cmdFlags.CfgGenesisFile, net.GenesisPath())
}
Expand Down
6 changes: 6 additions & 0 deletions go/runtime/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ const (
// RuntimeEnvironmentSGX specifies to run the runtime in SGX.
RuntimeEnvironmentSGX RuntimeEnvironment = "sgx"

// RuntimeEnvironmentSGXMock specifies to run the runtime in mocked SGX.
//
// Use of this runtime environment is only allowed if DebugDontBlameOasis flag is set.
RuntimeEnvironmentSGXMock RuntimeEnvironment = "sgx-mock"

// RuntimeEnvironmentELF specifies to run the runtime in the OS address space.
//
// Use of this runtime environment is only allowed if DebugDontBlameOasis flag is set.
Expand Down Expand Up @@ -175,6 +180,7 @@ func (c *Config) Validate() error {
if c.SGXLoader == "" {
return fmt.Errorf("sgx_loader must be set when using sgx environment")
}
case RuntimeEnvironmentSGXMock:
case RuntimeEnvironmentELF:
case RuntimeEnvironmentAuto:
default:
Expand Down
62 changes: 35 additions & 27 deletions go/runtime/host/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ const (
ctrlChannelBufferSize = 16
)

// GetSandboxConfigFunc is the function used to generate the sandbox configuration.
type GetSandboxConfigFunc func(cfg host.Config, socketPath, runtimeDir string) (process.Config, error)

// Config contains the sandbox provisioner configuration options.
type Config struct {
// GetSandboxConfig is a function that generates the sandbox configuration. In case it is not
// specified a default function is used.
GetSandboxConfig func(cfg host.Config, socketPath, runtimeDir string) (process.Config, error)
GetSandboxConfig GetSandboxConfigFunc

// HostInfo provides information about the host environment.
HostInfo *protocol.HostInfo
Expand Down Expand Up @@ -613,6 +616,36 @@ func (r *sandboxedRuntime) manager() {
}
}

// DefaultGetSandboxConfig is the default function for generating sandbox configuration.
func DefaultGetSandboxConfig(logger *logging.Logger, sandboxBinaryPath string) GetSandboxConfigFunc {
return func(hostCfg host.Config, socketPath, _ string) (process.Config, error) {
if numComps := len(hostCfg.Components); numComps != 1 {
return process.Config{}, fmt.Errorf("expected a single component (got %d)", numComps)
}
comp := hostCfg.Bundle.Manifest.GetComponentByID(hostCfg.Components[0])
if comp == nil {
return process.Config{}, fmt.Errorf("component '%s' not available", hostCfg.Components[0])
}

logWrapper := host.NewRuntimeLogWrapper(
logger,
"runtime_id", hostCfg.Bundle.Manifest.ID,
"runtime_name", hostCfg.Bundle.Manifest.Name,
"component", comp.Kind,
)
return process.Config{
Path: hostCfg.Bundle.ExplodedPath(hostCfg.Bundle.ExplodedDataDir, comp.Executable),
Env: map[string]string{
"OASIS_WORKER_HOST": socketPath,
},
SandboxBinaryPath: sandboxBinaryPath,
Stdout: logWrapper,
Stderr: logWrapper,
AllowNetwork: comp.IsNetworkAllowed(),
}, nil
}
}

// New creates a new runtime provisioner that uses a local process sandbox.
func New(cfg Config) (host.Provisioner, error) {
// Use a default Logger if none was provided.
Expand All @@ -621,32 +654,7 @@ func New(cfg Config) (host.Provisioner, error) {
}
// Use a default GetSandboxConfig if none was provided.
if cfg.GetSandboxConfig == nil {
cfg.GetSandboxConfig = func(hostCfg host.Config, socketPath, _ string) (process.Config, error) {
if numComps := len(hostCfg.Components); numComps != 1 {
return process.Config{}, fmt.Errorf("expected a single component (got %d)", numComps)
}
comp := hostCfg.Bundle.Manifest.GetComponentByID(hostCfg.Components[0])
if comp == nil {
return process.Config{}, fmt.Errorf("component '%s' not available", hostCfg.Components[0])
}

logWrapper := host.NewRuntimeLogWrapper(
cfg.Logger,
"runtime_id", hostCfg.Bundle.Manifest.ID,
"runtime_name", hostCfg.Bundle.Manifest.Name,
"component", comp.Kind,
)
return process.Config{
Path: hostCfg.Bundle.ExplodedPath(hostCfg.Bundle.ExplodedDataDir, comp.Executable),
Env: map[string]string{
"OASIS_WORKER_HOST": socketPath,
},
SandboxBinaryPath: cfg.SandboxBinaryPath,
Stdout: logWrapper,
Stderr: logWrapper,
AllowNetwork: comp.IsNetworkAllowed(),
}, nil
}
cfg.GetSandboxConfig = DefaultGetSandboxConfig(cfg.Logger, cfg.SandboxBinaryPath)
}
// Make sure host environment information was provided in HostInfo.
if cfg.HostInfo == nil {
Expand Down
95 changes: 95 additions & 0 deletions go/runtime/host/sgx/mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package sgx

import (
"context"
"fmt"
"time"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/cbor"
"github.com/oasisprotocol/oasis-core/go/common/node"
"github.com/oasisprotocol/oasis-core/go/common/sgx/pcs"
sgxQuote "github.com/oasisprotocol/oasis-core/go/common/sgx/quote"
"github.com/oasisprotocol/oasis-core/go/common/version"
consensus "github.com/oasisprotocol/oasis-core/go/consensus/api"
"github.com/oasisprotocol/oasis-core/go/runtime/host/protocol"
)

type teeStateMock struct{}

func (ec *teeStateMock) Init(ctx context.Context, sp *sgxProvisioner, _ common.Namespace, _ version.Version) ([]byte, error) {
// Check whether the consensus layer even supports ECDSA attestations.
regParams, err := sp.consensus.Registry().ConsensusParameters(ctx, consensus.HeightLatest)
if err != nil {
return nil, fmt.Errorf("unable to determine registry consensus parameters: %w", err)
}
if regParams.TEEFeatures == nil || !regParams.TEEFeatures.SGX.PCS {
return nil, fmt.Errorf("ECDSA not supported by the registry")
}

// Generate mock QE target info.
var targetInfo [512]byte

return targetInfo[:], nil
}

func (ec *teeStateMock) Update(ctx context.Context, sp *sgxProvisioner, conn protocol.Connection, report []byte, _ string) ([]byte, error) {
rawQuote, err := pcs.NewMockQuote(report)
if err != nil {
return nil, fmt.Errorf("failed to get quote: %w", err)
}

var quote pcs.Quote
if err = quote.UnmarshalBinary(rawQuote); err != nil {
return nil, fmt.Errorf("failed to parse quote: %w", err)
}

// Check what information we need to retrieve based on what is in the quote.
qs, ok := quote.Signature.(*pcs.QuoteSignatureECDSA_P256)
if !ok {
return nil, fmt.Errorf("unsupported attestation key type: %s", quote.Signature.AttestationKeyType())
}

// Verify PCK certificate and extract the information required to get the TCB bundle.
pckInfo, err := qs.VerifyPCK(time.Now())
if err != nil {
return nil, fmt.Errorf("PCK verification failed: %w", err)
}

tcbBundle, err := sp.pcs.GetTCBBundle(ctx, pckInfo.FMSPC, pcs.UpdateStandard)
if err != nil {
return nil, err
}

// Prepare quote structure.
q := sgxQuote.Quote{
PCS: &pcs.QuoteBundle{
Quote: rawQuote,
TCB: *tcbBundle,
},
}

// Call the runtime with the quote and TCB bundle.
rspBody, err := conn.Call(
ctx,
&protocol.Body{
RuntimeCapabilityTEERakQuoteRequest: &protocol.RuntimeCapabilityTEERakQuoteRequest{
Quote: q,
},
},
)
if err != nil {
return nil, fmt.Errorf("error while configuring quote: %w", err)
}
rsp := rspBody.RuntimeCapabilityTEERakQuoteResponse
if rsp == nil {
return nil, fmt.Errorf("unexpected response from runtime")
}

return cbor.Marshal(node.SGXAttestation{
Versioned: cbor.NewVersioned(node.LatestSGXAttestationVersion),
Quote: q,
Height: rsp.Height,
Signature: rsp.Signature,
}), nil
}
Loading

0 comments on commit 22eaf6b

Please sign in to comment.