From 7048cbb19d5a315c33e6aa82a7a6df9a3f5d5e81 Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Wed, 28 Aug 2024 13:39:53 +0200 Subject: [PATCH] go/runtime: Support bundle components without ELF binary --- .changelog/5838.feature.md | 1 + go/runtime/bundle/manifest.go | 24 ++++++--- go/runtime/registry/config.go | 94 ++++++++++++++++++++--------------- 3 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 .changelog/5838.feature.md diff --git a/.changelog/5838.feature.md b/.changelog/5838.feature.md new file mode 100644 index 00000000000..6349f8a33e7 --- /dev/null +++ b/.changelog/5838.feature.md @@ -0,0 +1 @@ +go/runtime: Support bundle components without ELF binary diff --git a/go/runtime/bundle/manifest.go b/go/runtime/bundle/manifest.go index c1b5e33164c..e6cb2d6d3a9 100644 --- a/go/runtime/bundle/manifest.go +++ b/go/runtime/bundle/manifest.go @@ -46,6 +46,11 @@ func (m *Manifest) Hash() hash.Hash { return hash.NewFrom(m) } +// IsLegacy returns true iff this is a legacy manifest that defines executables at the top level. +func (m *Manifest) IsLegacy() bool { + return len(m.Executable) > 0 || m.SGX != nil +} + // Validate validates the manifest structure for well-formedness. func (m *Manifest) Validate() error { byID := make(map[component.ID]struct{}) @@ -62,7 +67,7 @@ func (m *Manifest) Validate() error { } } - if _, ok := byID[component.ID_RONL]; ok && len(m.Executable) > 0 { + if _, ok := byID[component.ID_RONL]; ok && m.IsLegacy() { return fmt.Errorf("manifest defines both legacy and componentized RONL component") } @@ -105,7 +110,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.IsRONL() && len(m.Executable) > 0 { + if id.IsRONL() && m.IsLegacy() { return &Component{ Kind: component.RONL, Executable: m.Executable, @@ -141,8 +146,8 @@ type Component struct { // provided by a runtime. Name string `json:"name,omitempty"` - // Executable is the name of the runtime ELF executable file. - Executable string `json:"executable"` + // Executable is the name of the runtime ELF executable file if any. + Executable string `json:"executable,omitempty"` // SGX is the SGX specific manifest metadata if any. SGX *SGXMetadata `json:"sgx,omitempty"` @@ -164,9 +169,6 @@ func (c *Component) Matches(id component.ID) bool { // Validate validates the component structure for well-formedness. func (c *Component) Validate() error { - if c.Executable == "" { - return fmt.Errorf("executable must be set") - } if c.SGX != nil { err := c.SGX.Validate() if err != nil { @@ -179,6 +181,9 @@ func (c *Component) Validate() error { if c.Name != "" { return fmt.Errorf("RONL component must have an empty name") } + if c.Executable == "" { + return fmt.Errorf("RONL component must define an executable") + } if c.Disabled { return fmt.Errorf("RONL component cannot be disabled") } @@ -200,3 +205,8 @@ func (c *Component) IsNetworkAllowed() bool { return false } } + +// IsTEERequired returns true iff the component only provides TEE executables. +func (c *Component) IsTEERequired() bool { + return c.Executable == "" && c.SGX != nil +} diff --git a/go/runtime/registry/config.go b/go/runtime/registry/config.go index f6c7bd56afe..3098445b5dc 100644 --- a/go/runtime/registry/config.go +++ b/go/runtime/registry/config.go @@ -102,7 +102,62 @@ func newConfig( //nolint: gocyclo // Check if any runtimes are configured to be hosted. if haveSetRuntimes || (cmdFlags.DebugDontBlameOasis() && viper.IsSet(CfgDebugMockIDs)) { + // By default start with the environment specified in configuration. runtimeEnv := config.GlobalConfig.Runtime.Environment + + // Preprocess runtimes to separate detached from non-detached. + type nameKey struct { + runtime common.Namespace + comp component.ID + } + + var ( + regularBundles []*bundle.Bundle + err error + ) + detachedBundles := make(map[common.Namespace][]*bundle.Bundle) + existingNames := make(map[nameKey]struct{}) + for _, path := range config.GlobalConfig.Runtime.Paths { + var bnd *bundle.Bundle + if bnd, err = bundle.Open(path); err != nil { + return nil, fmt.Errorf("failed to load runtime bundle '%s': %w", path, err) + } + if err = bnd.WriteExploded(dataDir); err != nil { + return nil, fmt.Errorf("failed to explode runtime bundle '%s': %w", path, err) + } + // Release resources as the bundle has been exploded anyway. + bnd.Data = nil + + switch bnd.Manifest.IsDetached() { + case false: + // A regular non-detached bundle that has the RONL component. + regularBundles = append(regularBundles, bnd) + case true: + // A detached bundle without the RONL component that needs to be attached. + detachedBundles[bnd.Manifest.ID] = append(detachedBundles[bnd.Manifest.ID], bnd) + + // Ensure there are no name conflicts among the components. + for compID := range bnd.Manifest.GetAvailableComponents() { + nk := nameKey{bnd.Manifest.ID, compID} + if _, ok := existingNames[nk]; ok { + return nil, fmt.Errorf("duplicate component '%s' for runtime '%s'", compID, bnd.Manifest.ID) + } + existingNames[nk] = struct{}{} + } + } + + // If the runtime environment is set to automatic selection and a bundle has a component + // that requires the use of a TEE, force a TEE environment to simplify configuration. + if runtimeEnv == rtConfig.RuntimeEnvironmentAuto { + for _, comp := range bnd.Manifest.GetAvailableComponents() { + if comp.IsTEERequired() { + runtimeEnv = rtConfig.RuntimeEnvironmentSGX + break + } + } + } + } + isEnvSGX := runtimeEnv == rtConfig.RuntimeEnvironmentSGX || runtimeEnv == rtConfig.RuntimeEnvironmentSGXMock forceNoSGX := (config.GlobalConfig.Mode.IsClientOnly() && !isEnvSGX) || (cmdFlags.DebugDontBlameOasis() && runtimeEnv == rtConfig.RuntimeEnvironmentELF) @@ -226,45 +281,6 @@ func newConfig( //nolint: gocyclo }) } - // Preprocess runtimes to separate detached from non-detached. - type nameKey struct { - runtime common.Namespace - comp component.ID - } - - var regularBundles []*bundle.Bundle - detachedBundles := make(map[common.Namespace][]*bundle.Bundle) - existingNames := make(map[nameKey]struct{}) - for _, path := range config.GlobalConfig.Runtime.Paths { - var bnd *bundle.Bundle - if bnd, err = bundle.Open(path); err != nil { - return nil, fmt.Errorf("failed to load runtime bundle '%s': %w", path, err) - } - if err = bnd.WriteExploded(dataDir); err != nil { - return nil, fmt.Errorf("failed to explode runtime bundle '%s': %w", path, err) - } - // Release resources as the bundle has been exploded anyway. - bnd.Data = nil - - switch bnd.Manifest.IsDetached() { - case false: - // A regular non-detached bundle that has the RONL component. - regularBundles = append(regularBundles, bnd) - case true: - // A detached bundle without the RONL component that needs to be attached. - detachedBundles[bnd.Manifest.ID] = append(detachedBundles[bnd.Manifest.ID], bnd) - - // Ensure there are no name conflicts among the components. - for compID := range bnd.Manifest.GetAvailableComponents() { - nk := nameKey{bnd.Manifest.ID, compID} - if _, ok := existingNames[nk]; ok { - return nil, fmt.Errorf("duplicate component '%s' for runtime '%s'", compID, bnd.Manifest.ID) - } - existingNames[nk] = struct{}{} - } - } - } - // Configure runtimes. rh.Runtimes = make(map[common.Namespace]map[version.Version]*runtimeHost.Config) for _, bnd := range regularBundles {