Skip to content

Commit 2c27d10

Browse files
authored
Merge pull request #335 from oasisprotocol/kostko/feature/rofl-tdx-podman
feat(cmd/rofl): Add TDX container build support
2 parents 2a03a8f + 64231c6 commit 2c27d10

File tree

20 files changed

+2053
-557
lines changed

20 files changed

+2053
-557
lines changed

build/rofl/manifest.go

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
package rofl
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"gopkg.in/yaml.v3"
11+
12+
"github.com/oasisprotocol/oasis-core/go/common/version"
13+
14+
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/rofl"
15+
)
16+
17+
// ManifestFileNames are the manifest file names that are tried when loading the manifest.
18+
var ManifestFileNames = []string{
19+
"rofl.yaml",
20+
"rofl.yml",
21+
}
22+
23+
// Supported ROFL app kinds.
24+
const (
25+
AppKindRaw = "raw"
26+
AppKindContainer = "container"
27+
)
28+
29+
// Supported TEE types.
30+
const (
31+
TEETypeSGX = "sgx"
32+
TEETypeTDX = "tdx"
33+
)
34+
35+
// Well-known scripts.
36+
const (
37+
ScriptBuildPre = "build-pre"
38+
ScriptBuildPost = "build-post"
39+
ScriptBundlePost = "bundle-post"
40+
)
41+
42+
// Manifest is the ROFL app manifest that configures various aspects of the app in a single place.
43+
type Manifest struct {
44+
// Name is the human readable ROFL app name.
45+
Name string `yaml:"name" json:"name"`
46+
// Version is the ROFL app version.
47+
Version string `yaml:"version" json:"version"`
48+
// TEE is the type of TEE to build for.
49+
TEE string `yaml:"tee" json:"tee"`
50+
// Kind is the kind of ROFL app to build.
51+
Kind string `yaml:"kind" json:"kind"`
52+
// Resources are the requested ROFL app resources.
53+
Resources ResourcesConfig `yaml:"resources" json:"resources"`
54+
// Artifacts are the optional artifact location overrides.
55+
Artifacts *ArtifactsConfig `yaml:"artifacts,omitempty" json:"artifacts,omitempty"`
56+
57+
// Deployments are the ROFL app deployments.
58+
Deployments map[string]*Deployment `yaml:"deployments" json:"deployments"`
59+
60+
// Scripts are custom scripts that are executed by the build system at specific stages.
61+
Scripts map[string]string `yaml:"scripts,omitempty" json:"scripts,omitempty"`
62+
63+
// sourceFn is the filename from which the manifest has been loaded.
64+
sourceFn string
65+
}
66+
67+
// ManifestExists checks whether a manifest file exist. No attempt is made to load, parse or
68+
// validate any of the found manifest files.
69+
func ManifestExists() bool {
70+
for _, fn := range ManifestFileNames {
71+
_, err := os.Stat(fn)
72+
switch {
73+
case errors.Is(err, os.ErrNotExist):
74+
continue
75+
default:
76+
return true
77+
}
78+
}
79+
return false
80+
}
81+
82+
// LoadManifest attempts to find and load the ROFL app manifest from a local file.
83+
func LoadManifest() (*Manifest, error) {
84+
for _, fn := range ManifestFileNames {
85+
f, err := os.Open(fn)
86+
switch {
87+
case err == nil:
88+
case errors.Is(err, os.ErrNotExist):
89+
continue
90+
default:
91+
return nil, fmt.Errorf("failed to load manifest from '%s': %w", fn, err)
92+
}
93+
94+
var m Manifest
95+
dec := yaml.NewDecoder(f)
96+
if err = dec.Decode(&m); err != nil {
97+
f.Close()
98+
return nil, fmt.Errorf("malformed manifest '%s': %w", fn, err)
99+
}
100+
if err = m.Validate(); err != nil {
101+
f.Close()
102+
return nil, fmt.Errorf("invalid manifest '%s': %w", fn, err)
103+
}
104+
m.sourceFn, _ = filepath.Abs(f.Name()) // Record source filename.
105+
106+
f.Close()
107+
return &m, nil
108+
}
109+
return nil, fmt.Errorf("no ROFL app manifest found (tried: %s)", strings.Join(ManifestFileNames, ", "))
110+
}
111+
112+
// Validate validates the manifest for correctness.
113+
func (m *Manifest) Validate() error {
114+
if len(m.Name) == 0 {
115+
return fmt.Errorf("name cannot be empty")
116+
}
117+
118+
if len(m.Version) == 0 {
119+
return fmt.Errorf("version cannot be empty")
120+
}
121+
if _, err := version.FromString(m.Version); err != nil {
122+
return fmt.Errorf("malformed version: %w", err)
123+
}
124+
125+
switch m.TEE {
126+
case TEETypeSGX, TEETypeTDX:
127+
default:
128+
return fmt.Errorf("unsupported TEE type: %s", m.TEE)
129+
}
130+
131+
switch m.Kind {
132+
case AppKindRaw:
133+
case AppKindContainer:
134+
if m.TEE != TEETypeTDX {
135+
return fmt.Errorf("containers are only supported under TDX")
136+
}
137+
default:
138+
return fmt.Errorf("unsupported app kind: %s", m.Kind)
139+
}
140+
141+
if err := m.Resources.Validate(); err != nil {
142+
return fmt.Errorf("bad resources config: %w", err)
143+
}
144+
145+
for name, d := range m.Deployments {
146+
if d == nil {
147+
return fmt.Errorf("bad deployment: %s", name)
148+
}
149+
if err := d.Validate(); err != nil {
150+
return fmt.Errorf("bad deployment '%s': %w", name, err)
151+
}
152+
}
153+
if _, ok := m.Deployments[DefaultDeploymentName]; !ok {
154+
return fmt.Errorf("must define at least the '%s' deployment", DefaultDeploymentName)
155+
}
156+
157+
return nil
158+
}
159+
160+
// SourceFileName returns the filename of the manifest file from which the manifest was loaded or
161+
// an empty string in case the filename is not available.
162+
func (m *Manifest) SourceFileName() string {
163+
return m.sourceFn
164+
}
165+
166+
// Save serializes the manifest and writes it to the file returned by `SourceFileName`, overwriting
167+
// any previous manifest.
168+
//
169+
// If no previous source filename is available, a default one is set.
170+
func (m *Manifest) Save() error {
171+
if m.sourceFn == "" {
172+
m.sourceFn = ManifestFileNames[0]
173+
}
174+
175+
f, err := os.Create(m.sourceFn)
176+
if err != nil {
177+
return err
178+
}
179+
defer f.Close()
180+
181+
enc := yaml.NewEncoder(f)
182+
enc.SetIndent(2)
183+
return enc.Encode(m)
184+
}
185+
186+
// DefaultDeploymentName is the name of the default deployment that must always be defined and is
187+
// used in case no deployment is passed.
188+
const DefaultDeploymentName = "default"
189+
190+
// Deployment describes a single ROFL app deployment.
191+
type Deployment struct {
192+
// AppID is the Bech32-encoded ROFL app ID.
193+
AppID string `yaml:"app_id,omitempty" json:"app_id,omitempty"`
194+
// Network is the identifier of the network to deploy to.
195+
Network string `yaml:"network" json:"network"`
196+
// ParaTime is the identifier of the paratime to deploy to.
197+
ParaTime string `yaml:"paratime" json:"paratime"`
198+
// Admin is the identifier of the admin account.
199+
Admin string `yaml:"admin,omitempty" json:"admin,omitempty"`
200+
// Debug is a flag denoting whether this is a debuggable deployment.
201+
Debug bool `yaml:"debug,omitempty" json:"debug,omitempty"`
202+
// TrustRoot is the optional trust root configuration.
203+
TrustRoot *TrustRootConfig `yaml:"trust_root,omitempty" json:"trust_root,omitempty"`
204+
// Policy is the ROFL app policy.
205+
Policy *rofl.AppAuthPolicy `yaml:"policy,omitempty" json:"policy,omitempty"`
206+
// Metadata contains custom metadata.
207+
Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
208+
// Secrets contains encrypted secrets.
209+
Secrets []*SecretConfig `yaml:"secrets,omitempty" json:"secrets,omitempty"`
210+
}
211+
212+
// Validate validates the manifest for correctness.
213+
func (d *Deployment) Validate() error {
214+
if len(d.AppID) > 0 {
215+
var appID rofl.AppID
216+
if err := appID.UnmarshalText([]byte(d.AppID)); err != nil {
217+
return fmt.Errorf("malformed app ID: %w", err)
218+
}
219+
}
220+
if d.Network == "" {
221+
return fmt.Errorf("network cannot be empty")
222+
}
223+
if d.ParaTime == "" {
224+
return fmt.Errorf("paratime cannot be empty")
225+
}
226+
for _, s := range d.Secrets {
227+
if err := s.Validate(); err != nil {
228+
return fmt.Errorf("bad secret: %w", err)
229+
}
230+
}
231+
return nil
232+
}
233+
234+
// HasAppID returns true iff the deployment has an application identifier set.
235+
func (d *Deployment) HasAppID() bool {
236+
return len(d.AppID) > 0
237+
}
238+
239+
// TrustRootConfig is the trust root configuration.
240+
type TrustRootConfig struct {
241+
// Height is the consensus layer block height where to take the trust root.
242+
Height uint64 `yaml:"height,omitempty" json:"height,omitempty"`
243+
// Hash is the consensus layer block header hash corresponding to the passed height.
244+
Hash string `yaml:"hash,omitempty" json:"hash,omitempty"`
245+
}
246+
247+
// ResourcesConfig is the resources configuration.
248+
type ResourcesConfig struct {
249+
// Memory is the amount of memory needed by the app in megabytes.
250+
Memory uint64 `yaml:"memory" json:"memory"`
251+
// CPUCount is the number of vCPUs needed by the app.
252+
CPUCount uint8 `yaml:"cpus" json:"cpus"`
253+
// Storage is the storage configuration.
254+
Storage *StorageConfig `yaml:"storage,omitempty" json:"storage,omitempty"`
255+
}
256+
257+
// Validate validates the resources configuration for correctness.
258+
func (r *ResourcesConfig) Validate() error {
259+
if r.Memory < 16 {
260+
return fmt.Errorf("memory size must be at least 16M")
261+
}
262+
if r.CPUCount < 1 {
263+
return fmt.Errorf("vCPU count must be at least 1")
264+
}
265+
if r.Storage != nil {
266+
err := r.Storage.Validate()
267+
if err != nil {
268+
return fmt.Errorf("bad storage config: %w", err)
269+
}
270+
}
271+
return nil
272+
}
273+
274+
// Supported storage kinds.
275+
const (
276+
StorageKindNone = "none"
277+
StorageKindDiskEphemeral = "disk-ephemeral"
278+
StorageKindDiskPersistent = "disk-persistent"
279+
StorageKindRAM = "ram"
280+
)
281+
282+
// StorageConfig is the storage configuration.
283+
type StorageConfig struct {
284+
// Kind is the storage kind.
285+
Kind string `yaml:"kind" json:"kind"`
286+
// Size is the amount of storage in megabytes.
287+
Size uint64 `yaml:"size" json:"size"`
288+
}
289+
290+
// Validate validates the storage configuration for correctness.
291+
func (e *StorageConfig) Validate() error {
292+
switch e.Kind {
293+
case StorageKindNone, StorageKindDiskEphemeral, StorageKindDiskPersistent, StorageKindRAM:
294+
default:
295+
return fmt.Errorf("unsupported storage kind: %s", e.Kind)
296+
}
297+
298+
if e.Size < 16 {
299+
return fmt.Errorf("storage size must be at least 16M")
300+
}
301+
return nil
302+
}
303+
304+
// ArtifactsConfig is the artifact location override configuration.
305+
type ArtifactsConfig struct {
306+
// Firmware is the URI/path to the firmware artifact (empty to use default).
307+
Firmware string `yaml:"firmware,omitempty" json:"firmware,omitempty"`
308+
// Kernel is the URI/path to the kernel artifact (empty to use default).
309+
Kernel string `yaml:"kernel,omitempty" json:"kernel,omitempty"`
310+
// Stage2 is the URI/path to the stage 2 disk artifact (empty to use default).
311+
Stage2 string `yaml:"stage2,omitempty" json:"stage2,omitempty"`
312+
// Container is the container artifacts configuration.
313+
Container ContainerArtifactsConfig `yaml:"container,omitempty" json:"container,omitempty"`
314+
}
315+
316+
// ContainerArtifactsConfig is the container artifacts configuration.
317+
type ContainerArtifactsConfig struct {
318+
// Runtime is the URI/path to the container runtime artifact (empty to use default).
319+
Runtime string `yaml:"runtime,omitempty" json:"runtime,omitempty"`
320+
// Compose is the URI/path to the docker-compose.yaml artifact (empty to use default).
321+
Compose string `yaml:"compose,omitempty" json:"compose,omitempty"`
322+
}

0 commit comments

Comments
 (0)