Skip to content

Commit

Permalink
Merge pull request #5707 from oasisprotocol/kostko/feature/rofl-detached
Browse files Browse the repository at this point in the history
Support detached ROFL components
  • Loading branch information
kostko authored Jun 4, 2024
2 parents ef3d5ec + ef6ccb5 commit 1c62bbb
Show file tree
Hide file tree
Showing 15 changed files with 257 additions and 47 deletions.
11 changes: 11 additions & 0 deletions .changelog/5707.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Support detached ROFL components

Previously each bundle that contained one or more ROFL components also
needed to contain the exact version of the RONL component it was
attaching to.

This is somewhat awkward to use when we assume a more decentralized
development and deployment of ROFL applications. This commit adds support
for detached ROFL components where the bundle only contains the ROFL and
oasis-node then automatically gets the appropriate RONL component from
another bundle.
29 changes: 21 additions & 8 deletions go/runtime/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,19 +280,32 @@ func (bnd *Bundle) Write(fn string) error {
return nil
}

// ExplodedPath returns the path under the data directory that contains
// all of the exploded bundles.
// ExplodedPath returns the path under the data directory that contains all of the exploded bundles.
func ExplodedPath(dataDir string) string {
return filepath.Join(dataDir, "runtimes", "bundles")
}

// ExplodedPath returns the path that the corresponding asset will be
// written to via WriteExploded.
// DetachedExplodedPath returns the path under the data directory that contains all of the detached
// exploded bundles.
func DetachedExplodedPath(dataDir string) string {
return filepath.Join(ExplodedPath(dataDir), "detached")
}

// ExplodedPath returns the path that the corresponding asset will be written to via WriteExploded.
func (bnd *Bundle) ExplodedPath(dataDir, fn string) string {
// DATADIR/runtimes/bundles/runtimeID-version
subDir := filepath.Join(ExplodedPath(dataDir),
fmt.Sprintf("%s-%s", bnd.Manifest.ID, bnd.Manifest.Version),
)
var subDir string
switch bnd.Manifest.IsDetached() {
case false:
// DATADIR/runtimes/bundles/manifestHash
subDir = filepath.Join(ExplodedPath(dataDir),
bnd.Manifest.Hash().String(),
)
case true:
// DATADIR/runtimes/bundles/detached/manifestHash
subDir = filepath.Join(DetachedExplodedPath(dataDir),
bnd.Manifest.Hash().String(),
)
}

if fn == "" {
return subDir
Expand Down
97 changes: 92 additions & 5 deletions go/runtime/bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/runtime/bundle/component"
)

func TestBundle(t *testing.T) {
Expand All @@ -32,15 +33,22 @@ func TestBundle(t *testing.T) {
}
return id
}(),
Executable: "runtime.bin",
SGX: &SGXMetadata{
Executable: "runtime.sgx",
Components: []*Component{
{
Kind: component.RONL,
Executable: "runtime.bin",
SGX: &SGXMetadata{
Executable: "runtime.sgx",
},
},
},
}
bundle := &Bundle{
Manifest: manifest,
}

require.False(t, manifest.IsDetached(), "manifest with RONL component should not be detached")

t.Run("Add_Write", func(t *testing.T) {
// Generate random assets.
randomBuffer := func() []byte {
Expand All @@ -50,9 +58,9 @@ func TestBundle(t *testing.T) {
return b
}

err := bundle.Add(manifest.Executable, execBuf)
err := bundle.Add(manifest.Components[0].Executable, execBuf)
require.NoError(t, err, "bundle.Add(elf)")
err = bundle.Add(manifest.SGX.Executable, randomBuffer())
err = bundle.Add(manifest.Components[0].SGX.Executable, randomBuffer())
require.NoError(t, err, "bundle.Add(sgx)")

err = bundle.Write(bundleFn)
Expand Down Expand Up @@ -94,3 +102,82 @@ func TestBundle(t *testing.T) {
require.NoError(t, err, "WriteExploded(again)")
})
}

func TestDeatchedBundle(t *testing.T) {
execFile := os.Args[0]
execBuf, err := os.ReadFile(execFile)
if err != nil {
t.Fatalf("failed to read test executable %s: %v", execFile, err)
}

// Create a synthetic bundle.
//
// Assets will be populated during the Add/Write combined test.
tmpDir := t.TempDir()
bundleFn := filepath.Join(tmpDir, "detached-bundle.orc")
manifest := &Manifest{
Name: "test-runtime",
ID: func() common.Namespace {
var id common.Namespace
if err := id.UnmarshalHex("c000000000000000ffffffffffffffffffffffffffffffffffffffffffffffff"); err != nil {
panic("failed to unmarshal id")
}
return id
}(),
Components: []*Component{
// No RONL component in the manifest.
{
Kind: component.ROFL,
Executable: "runtime.bin",
SGX: &SGXMetadata{
Executable: "runtime.sgx",
},
},
},
}
bundle := &Bundle{
Manifest: manifest,
}

require.True(t, manifest.IsDetached(), "manifest without RONL component should be detached")

t.Run("Add_Write", func(t *testing.T) {
// Generate random assets.
randomBuffer := func() []byte {
b := make([]byte, 1024*256)
_, err := rand.Read(b)
require.NoError(t, err, "rand.Read")
return b
}

err := bundle.Add(manifest.Components[0].Executable, execBuf)
require.NoError(t, err, "bundle.Add(elf)")
err = bundle.Add(manifest.Components[0].SGX.Executable, randomBuffer())
require.NoError(t, err, "bundle.Add(sgx)")

err = bundle.Write(bundleFn)
require.NoError(t, err, "bundle.Write")
})

t.Run("Open", func(t *testing.T) {
bundle2, err := Open(bundleFn)
require.NoError(t, err, "Open")

// Ignore the manifest, the bundle we used to create the file
// will not have it.
delete(bundle2.Manifest.Digests, manifestName)
delete(bundle2.Data, manifestName)

require.EqualValues(t, bundle, bundle2, "opened bundle mismatch")
})

t.Run("Explode", func(t *testing.T) {
err := bundle.WriteExploded(tmpDir)
require.NoError(t, err, "WriteExploded")

// Abuse the fact that we do an integrity check if the bundle
// is already exploded.
err = bundle.WriteExploded(tmpDir)
require.NoError(t, err, "WriteExploded(again)")
})
}
5 changes: 5 additions & 0 deletions go/runtime/bundle/component/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,10 @@ func (c *ID) UnmarshalText(text []byte) error {
return nil
}

// IsRONL returns true iff the component identifier is the special RONL component identifier.
func (c ID) IsRONL() bool {
return c == ID_RONL
}

// ID_RONL is the identifier of the RONL component.
var ID_RONL = ID{Kind: RONL, Name: ""} //nolint: revive
22 changes: 14 additions & 8 deletions go/runtime/bundle/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ type Manifest struct {
Digests map[string]hash.Hash `json:"digests"`
}

// Hash returns a cryptographic hash of the CBOR-serialized manifest.
func (m *Manifest) Hash() hash.Hash {
return hash.NewFrom(m)
}

// Validate validates the manifest structure for well-formedness.
func (m *Manifest) Validate() error {
byID := make(map[component.ID]struct{})
Expand Down Expand Up @@ -69,22 +74,23 @@ func (m *Manifest) Validate() error {
}
}

// Ensure the RONL component is always defined.
if ronl := m.GetComponentByID(component.ID_RONL); ronl == nil {
return fmt.Errorf("runtime must define at least the RONL component")
}

return nil
}

// IsDetached returns true iff the manifest does not include a RONL component. Such bundles require
// that the RONL component is provided out-of-band (e.g. in a separate bundle).
func (m *Manifest) IsDetached() bool {
return m.GetComponentByID(component.ID_RONL) == nil
}

// GetAvailableComponents collects all of the available components into a map.
func (m *Manifest) GetAvailableComponents() map[component.ID]*Component {
availableComps := make(map[component.ID]*Component)
for _, comp := range m.Components {
availableComps[comp.ID()] = comp
}
if _, exists := availableComps[component.ID_RONL]; !exists {
// Needed for supporting legacy manifests -- always available, see Validate above.
if _, exists := availableComps[component.ID_RONL]; !exists && !m.IsDetached() {
// Needed for supporting legacy manifests.
availableComps[component.ID_RONL] = m.GetComponentByID(component.ID_RONL)
}
return availableComps
Expand All @@ -99,7 +105,7 @@ func (m *Manifest) GetComponentByID(id component.ID) *Component {
}

// We also support legacy manifests which define the RONL component at the top-level.
if id == component.ID_RONL && len(m.Executable) > 0 {
if id.IsRONL() && len(m.Executable) > 0 {
return &Component{
Kind: component.RONL,
Executable: m.Executable,
Expand Down
2 changes: 1 addition & 1 deletion go/runtime/host/composite/composite.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (c *composite) Call(ctx context.Context, body *protocol.Body) (*protocol.Bo
}

for id, comp := range c.comps {
if id == component.ID_RONL {
if id.IsRONL() {
continue // Already handled above.
}
if !shouldPropagateToComponent(body) {
Expand Down
13 changes: 13 additions & 0 deletions go/runtime/host/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package host

import (
"context"
"path/filepath"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/node"
Expand Down Expand Up @@ -38,6 +39,18 @@ type RuntimeBundle struct {

// ExplodedDataDir is the path to the data directory under which the bundle has been exploded.
ExplodedDataDir string

// ExplodedDetachedDirs are the paths to the data directories of detached components.
ExplodedDetachedDirs map[component.ID]string
}

// ExplodedPath returns the path where the given asset for the given component can be found.
func (bnd *RuntimeBundle) ExplodedPath(comp component.ID, fn string) string {
if detachedDir, ok := bnd.ExplodedDetachedDirs[comp]; ok {
return filepath.Join(detachedDir, fn)
}
// Default to the exploded bundle.
return bnd.Bundle.ExplodedPath(bnd.ExplodedDataDir, fn)
}

// Provisioner is the runtime provisioner interface.
Expand Down
2 changes: 1 addition & 1 deletion go/runtime/host/multi/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func (agg *Aggregate) Component(id component.ID) (host.Runtime, bool) {
if cr, ok := active.host.(host.CompositeRuntime); ok {
return cr.Component(id)
}
if id == component.ID_RONL {
if id.IsRONL() {
return active.host, true
}
return nil, false
Expand Down
11 changes: 7 additions & 4 deletions go/runtime/host/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,12 @@ func (r *sandboxedRuntime) startProcess() (err error) {
return fmt.Errorf("failed to initialize connection: %w", err)
}

// Make sure the version matches what is configured in the bundle.
if bndVersion := r.rtCfg.Bundle.Manifest.Version; *rtVersion != bndVersion {
return fmt.Errorf("version mismatch (runtime reported: %s bundle: %s)", *rtVersion, bndVersion)
if r.rtCfg.Components[0].IsRONL() {
// Make sure the version matches what is configured in the bundle. This check is skipped for
// non-RONL components to support detached bundles.
if bndVersion := r.rtCfg.Bundle.Manifest.Version; *rtVersion != bndVersion {
return fmt.Errorf("version mismatch (runtime reported: %s bundle: %s)", *rtVersion, bndVersion)
}
}

hp := &HostInitializerParams{
Expand Down Expand Up @@ -661,7 +664,7 @@ func DefaultGetSandboxConfig(logger *logging.Logger, sandboxBinaryPath string) G
"component", comp.Kind,
)
return process.Config{
Path: hostCfg.Bundle.ExplodedPath(hostCfg.Bundle.ExplodedDataDir, comp.Executable),
Path: hostCfg.Bundle.ExplodedPath(comp.ID(), comp.Executable),
Env: map[string]string{
"OASIS_WORKER_HOST": socketPath,
},
Expand Down
4 changes: 2 additions & 2 deletions go/runtime/host/sgx/sgx.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (s *sgxProvisioner) loadEnclaveBinaries(rtCfg host.Config, comp *bundle.Com
if comp.SGX == nil || comp.SGX.Executable == "" {
return nil, nil, fmt.Errorf("SGX executable not available in bundle")
}
sgxExecutablePath := rtCfg.Bundle.ExplodedPath(rtCfg.Bundle.ExplodedDataDir, comp.SGX.Executable)
sgxExecutablePath := rtCfg.Bundle.ExplodedPath(comp.ID(), comp.SGX.Executable)

var (
sig, sgxs []byte
Expand All @@ -198,7 +198,7 @@ func (s *sgxProvisioner) loadEnclaveBinaries(rtCfg host.Config, comp *bundle.Com
}

if comp.SGX.Signature != "" {
sgxSignaturePath := rtCfg.Bundle.ExplodedPath(rtCfg.Bundle.ExplodedDataDir, comp.SGX.Signature)
sgxSignaturePath := rtCfg.Bundle.ExplodedPath(comp.ID(), comp.SGX.Signature)
sig, err = os.ReadFile(sgxSignaturePath)
if err != nil {
return nil, nil, fmt.Errorf("failed to load SIGSTRUCT: %w", err)
Expand Down
Loading

0 comments on commit 1c62bbb

Please sign in to comment.