Skip to content

Commit

Permalink
AB#1317 Update Graphene to Gramine (#268)
Browse files Browse the repository at this point in the history
* Rename Graphene to Gramine

* Update gramine-hello sample

* Update gramine-nginx sample

* Update gramine-redis sample

* Adjust cli to new Gramine manifest format

Signed-off-by: Daniel Weiße <[email protected]>
  • Loading branch information
daniel-weisse authored Oct 27, 2021
1 parent a59d0e4 commit c270a92
Show file tree
Hide file tree
Showing 32 changed files with 643 additions and 619 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ To keep things simple, MarbleRun issues one concise remote attestation statement
### Supported runtimes
MarbleRun supports services built with one of the following frameworks:
* [EGo][ego]
* [Graphene][graphene]
* [Gramine][gramine]
* [Edgeless RT][edgelessrt]

More are coming soon.
Expand Down Expand Up @@ -66,15 +66,15 @@ We provide basic examples on how to build confidential apps with MarbleRun:

* See [helloworld](samples/helloworld) for an example in Go
* See [helloc++](samples/helloc++) for an example in C++
* See [graphene-hello](samples/graphene-hello) for an example using Graphene
* See [gramine-hello](samples/gramine-hello) for an example using Gramine
* See [occlum-hello](samples/occlum-hello) for an example using Occlum

### Advanced

In case you want to see how you can integrate popular existing solutions with MarbleRun, we provide more advanced examples:

* See [graphene-nginx](samples/graphene-nginx) for an example of converting an existing Graphene application to a Marble
* See [graphene-redis](samples/graphene-redis) for a distributed Redis example using Graphene
* See [gramine-nginx](samples/gramine-nginx) for an example of converting an existing Gramine application to a Marble
* See [gramine-redis](samples/gramine-redis) for a distributed Redis example using Gramine

### Confidential emoji voting

Expand All @@ -92,7 +92,7 @@ The popular [Linkerd][linkerd] service mesh uses the simple and scalable *emojiv
[go-pkg-badge]: https://pkg.go.dev/badge/github.com/edgelesssys/marblerun
[go-report-card]: https://goreportcard.com/report/github.com/edgelesssys/marblerun
[go-report-card-badge]: https://goreportcard.com/badge/github.com/edgelesssys/marblerun
[graphene]: https://github.com/oscarlab/graphene
[gramine]: https://github.com/gramineproject/gramine
[license-badge]: https://img.shields.io/github/license/edgelesssys/marblerun
[linkerd]: https://linkerd.io
[marblerunsh]: https://marblerun.sh
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* Trusted time via Roughtime
* EdgelessDB storage-backend plugin
* Microsoft Azure Attestation integration
* Graphene TTLS support
* Gramine TTLS support

## Long-term

Expand Down
140 changes: 104 additions & 36 deletions cli/cmd/graphenePrepare.go → cli/cmd/graminePrepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,60 @@ const premainName = "premain-libos"
// uuidName is the file name of a Marble's uuid
const uuidName = "uuid"

// commentMarbleRunAdditions holds the marker which is appended to the Graphene manifest before the performed additions
// commentMarbleRunAdditions holds the marker which is appended to the Gramine manifest before the performed additions
const commentMarbleRunAdditions = "\n# MARBLERUN -- auto generated configuration entries \n"

// longDescription is the help text shown for this command
const longDescription = `Modifies a Graphene manifest for use with MarbleRun.
const longDescription = `Modifies a Gramine manifest for use with MarbleRun.
This command tries to automatically adjust the required parameters in an already existing Graphene manifest template, simplifying the migration of your existing Graphene application to MarbleRun.
This command tries to automatically adjust the required parameters in an already existing Gramine manifest template, simplifying the migration of your existing Gramine application to MarbleRun.
Please note that you still need to manually create a MarbleRun manifest.
For more information about the requirements and changes performed, consult the documentation: https://edglss.cc/doc-mr-graphene
For more information about the requirements and changes performed, consult the documentation: https://edglss.cc/doc-mr-gramine
The parameter of this command is the path of the Graphene manifest template you want to modify.
The parameter of this command is the path of the Gramine manifest template you want to modify.
`

type diff struct {
manifestEntry string
alreadyExists bool
// type of the entry, one of {'string', 'array'}
entryType string
// content of the entry
manifestEntry string
}

func newGraphenePrepareCmd() *cobra.Command {
func newGraminePrepareCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "graphene-prepare",
Short: "Modifies a Graphene manifest for use with MarbleRun",
Use: "gramine-prepare",
Short: "Modifies a Gramine manifest for use with MarbleRun",
Long: longDescription,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
fileName := args[0]

return addToGrapheneManifest(fileName)
return addToGramineManifest(fileName)
},
SilenceUsage: true,
}

return cmd
}

func addToGrapheneManifest(fileName string) error {
// Read Graphene manifest and populate TOML tree
func addToGramineManifest(fileName string) error {
// Read Gramine manifest and populate TOML tree
fmt.Println("Reading file:", fileName)

file, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
if strings.Contains(string(file), premainName) || strings.Contains(string(file), "EDG_MARBLE_COORDINATOR_ADDR") ||
strings.Contains(string(file), "EDG_MARBLE_TYPE") || strings.Contains(string(file), "EDG_MARBLE_UUID_FILE") ||
strings.Contains(string(file), "EDG_MARBLE_DNS_NAMES") {
color.Yellow("The supplied manifest already contains changes for MarbleRun. Have you selected the correct file?")
return errors.New("manifest already contains MarbleRun changes")
}

tree, err := toml.LoadFile(fileName)
if os.IsNotExist(err) {
return fmt.Errorf("file does not exist: %v", fileName)
Expand Down Expand Up @@ -93,37 +108,51 @@ func parseTreeForChanges(tree *toml.Tree) (map[string]interface{}, map[string]in
original["sgx.remote_attestation"] = tree.Get("sgx.remote_attestation")
original["sgx.enclave_size"] = tree.Get("sgx.enclave_size")
original["sgx.thread_num"] = tree.Get("sgx.thread_num")
original["sgx.trusted_files.marblerun_premain"] = tree.Get("sgx.trusted_files.marblerun_premain")
original["sgx.allowed_files.marblerun_uuid"] = tree.Get("sgx.allowed_files.marblerun_uuid")
original["loader.env.EDG_MARBLE_COORDINATOR_ADDR"] = tree.Get("loader.env.EDG_MARBLE_COORDINATOR_ADDR")
original["loader.env.EDG_MARBLE_TYPE"] = tree.Get("loader.env.EDG_MARBLE_TYPE")
original["loader.env.EDG_MARBLE_UUID_FILE"] = tree.Get("loader.env.EDG_MARBLE_UUID_FILE")
original["loader.env.EDG_MARBLE_DNS_NAMES"] = tree.Get("loader.env.EDG_MARBLE_DNS_NAMES")

// Abort, if we cannot find an endpoint
// Abort, if we cannot find an entrypoint
if original["libos.entrypoint"] == nil {
return nil, nil, errors.New("cannot find libos.entrypoint")
}

// If MarbleRun already touched the manifest, abort.
if original["libos.entrypoint"].(string) == premainName || original["sgx.trusted_files.marblerun_premain"] != nil || original["sgx.allowed_files.marblerun_uuid"] != nil {
color.Yellow("The supplied manifest already contains changes for MarbleRun. Have you selected the correct file?")
return nil, nil, errors.New("manifest already contains MarbleRun changes")
// add premain and uuid files
if err := insertFile(original, changes, "trusted_files", premainName, tree); err != nil {
return nil, nil, err
}
if err := insertFile(original, changes, "allowed_files", uuidName, tree); err != nil {
return nil, nil, err
}

// Add premain-libos executable as trusted file & entry point
changes["libos.entrypoint"] = premainName
changes["sgx.trusted_files.marblerun_premain"] = "file:" + premainName

// Set original endpoint as argv0. If one exists, keep the old one
// Set original entrypoint as argv0. If one exists, keep the old one
if original["loader.argv0_override"] == nil {
changes["loader.argv0_override"] = original["libos.entrypoint"].(string)
}

// Enable use "insecure" host env (which delegates the "secure" handling to MarbleRun)
if original["loader.insecure__use_host_env"] == nil || original["loader.insecure__use_host_env"].(int64) == 0 {
changes["loader.insecure__use_host_env"] = 1
// If insecure host environment is disabled (which hopefully it is), specify the required passthrough variables
if original["loader.insecure__use_host_env"] == nil || !original["loader.insecure__use_host_env"].(bool) {
if original["loader.env.EDG_MARBLE_COORDINATOR_ADDR"] == nil {
changes["loader.env.EDG_MARBLE_COORDINATOR_ADDR"] = "{ passthrough = true }"
}
if original["loader.env.EDG_MARBLE_TYPE"] == nil {
changes["loader.env.EDG_MARBLE_TYPE"] = "{ passthrough = true }"
}
if original["loader.env.EDG_MARBLE_UUID_FILE"] == nil {
changes["loader.env.EDG_MARBLE_UUID_FILE"] = "{ passthrough = true }"
}
if original["loader.env.EDG_MARBLE_DNS_NAMES"] == nil {
changes["loader.env.EDG_MARBLE_DNS_NAMES"] = "{ passthrough = true }"
}
}

// Enable remote attestation
if original["sgx.remote_attestation"] == nil || original["sgx.remote_attestation"].(int64) == 0 {
changes["sgx.remote_attestation"] = 1
if original["sgx.remote_attestation"] == nil || !original["sgx.remote_attestation"].(bool) {
changes["sgx.remote_attestation"] = true
}

// Ensure at least 1024 MB of enclave memory for the premain Go runtime
Expand All @@ -140,9 +169,6 @@ func parseTreeForChanges(tree *toml.Tree) (map[string]interface{}, map[string]in
changes["sgx.thread_num"] = 16
}

// Add Marble UUID to allowed files
changes["sgx.allowed_files.marblerun_uuid"] = "file:" + uuidName

return original, changes, nil
}

Expand All @@ -159,8 +185,17 @@ func calculateChanges(original map[string]interface{}, updates map[string]interf
// Add quotation marks for strings, direct value if not
switch v := changedValue.(type) {
case string:
newDiff.entryType = "string"
newDiff.manifestEntry = fmt.Sprintf("%s = \"%v\"", index, v)
case []interface{}:
newDiff.entryType = "array"
newEntry := fmt.Sprintf("%s = [\n", index)
for _, val := range v {
newEntry = fmt.Sprintf("%s \"%v\",\n", newEntry, val)
}
newDiff.manifestEntry = fmt.Sprintf("%s]", newEntry)
default:
newDiff.entryType = "string"
newDiff.manifestEntry = fmt.Sprintf("%s = %v", index, v)
}
changeDiffs = append(changeDiffs, newDiff)
Expand All @@ -177,7 +212,7 @@ func calculateChanges(original map[string]interface{}, updates map[string]interf

// performChanges displays the suggested changes to the user and tries to automatically perform them
func performChanges(changeDiffs []diff, fileName string) error {
fmt.Println("\nMarbleRun suggests the following changes to your Graphene manifest:")
fmt.Println("\nMarbleRun suggests the following changes to your Gramine manifest:")
for _, entry := range changeDiffs {
if entry.alreadyExists {
color.Yellow(entry.manifestEntry)
Expand All @@ -197,7 +232,7 @@ func performChanges(changeDiffs []diff, fileName string) error {

directory := filepath.Dir(fileName)

// Read Graphene manifest as normal text file
// Read Gramine manifest as normal text file
manifestContentOriginal, err := ioutil.ReadFile(fileName)
if err != nil {
return err
Expand Down Expand Up @@ -225,7 +260,7 @@ func performChanges(changeDiffs []diff, fileName string) error {
}

fmt.Println("Downloading MarbleRun premain from GitHub...")
// Download MarbleRun premain for Graphene from GitHub
// Download MarbleRun premain for Gramine from GitHub
if err := downloadPremain(directory); err != nil {
color.Red("ERROR: Cannot download '%s' from GitHub. Please add the file manually.", premainName)
}
Expand Down Expand Up @@ -268,7 +303,7 @@ func downloadPremain(directory string) error {
For existing entries: Run a RegEx search, replace the line.
For new entries: Append to the end of the file.
NOTE: This only works for flat-mapped TOML configs.
These seem to be usually used for Graphene manifests.
These seem to be usually used for Gramine manifests.
However, TOML is quite flexible, and there are no TOML parsers out there which are style & comments preserving
So, if we do not have a flat-mapped config, this will fail at some point.
*/
Expand All @@ -281,13 +316,23 @@ func appendAndReplace(changeDiffs []diff, manifestContent []byte) ([]byte, error
// If a value was previously existing, we replace the existing entry
key := strings.Split(value.manifestEntry, " =")
regexKey := strings.ReplaceAll(key[0], ".", "\\.")
regex := regexp.MustCompile("(?m)^" + regexKey + "\\s?=.*$")
var regex *regexp.Regexp

switch value.entryType {
case "string":
regex = regexp.MustCompile("(?m)^" + regexKey + "\\s?=.*$")
case "array":
regex = regexp.MustCompile("(?m)^" + regexKey + "\\s?=([^\\]]*)\\]$")
default:
return nil, fmt.Errorf("unkown manifest entry type: %v", value.entryType)
}

// Check if we actually found the entry we searched for. If not, we might be dealing with a TOML file we cannot handle correctly without a full parser.
regexMatches := regex.FindAll(newManifestContent, -1)
if regexMatches == nil {
color.Red("ERROR: Cannot find specified entry. Your Graphene config might not be flat-mapped.")
color.Red("ERROR: Cannot find specified entry. Your Gramine config might not be flat-mapped.")
color.Red("MarbleRun can only automatically modify manifests using a flat hierarchy, as otherwise we would lose all styling & comments.")
color.Red("To continue, please manually perform the changes printed above in your Graphene manifest.")
color.Red("To continue, please manually perform the changes printed above in your Gramine manifest.")
return nil, errors.New("failed to detect position of config entry")
} else if len(regexMatches) > 1 {
color.Red("ERROR: Found multiple potential matches for automatic value substitution.")
Expand All @@ -310,3 +355,26 @@ func appendAndReplace(changeDiffs []diff, manifestContent []byte) ([]byte, error

return newManifestContent, nil
}

// insertFile checks what trusted/allowed file declaration is used in the manifest and inserts files accordingly
// trusted/allowed files are either present in legacy 'sgx.trusted_files.identifier = "file:/path/file"' format
// or in TOML-array format
func insertFile(original, changes map[string]interface{}, fileType, fileName string, tree *toml.Tree) error {
fileTree := tree.Get("sgx." + fileType)
switch fileTree.(type) {
case nil:
// No files are defined in the original manifest
changes["sgx."+fileType] = []interface{}{"file:" + fileName}
return nil
case *toml.Tree:
// legacy format
changes["sgx."+fileType+".marblerun_"+fileName] = "file:" + fileName
case []interface{}:
// TOML-array format, append file to the array
original["sgx."+fileType] = tree.Get("sgx." + fileType)
changes["sgx."+fileType] = append(original["sgx."+fileType].([]interface{}), "file:"+fileName)
default:
return errors.New("could not read files from Gramine manifest")
}
return nil
}
17 changes: 14 additions & 3 deletions cli/cmd/graphenePrepare_test.go → cli/cmd/graminePrepare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ import (

const someManifest = `
libos.entrypoint = "myapplication"
sgx.remote_attestation = 0
sgx.remote_attestation = false
# Some comment here in between
# This should not match: sgx.enclave_size - 2
# This should also not match: sgx.enclave_size = 24M
sgx.enclave_size = "128M"
sgx.trusted_files = [
"file:/usr/favorite.file",
"file:/usr/lib/important.so"
]
sgx.allowed_files.unimportant = "file:/usr/not_that_important.txt"
`

func TestCalculateChanges(t *testing.T) {
Expand Down Expand Up @@ -75,6 +80,7 @@ func TestParseTreeForChanges(t *testing.T) {
assert.GreaterOrEqual(changes["sgx.thread_num"], 16)
require.NoError(v.UnmarshalText([]byte(changes["sgx.enclave_size"].(string))))
assert.GreaterOrEqual(v.GBytes(), 1.00)
assert.Equal([]interface{}{"file:/usr/favorite.file", "file:/usr/lib/important.so", "file:premain-libos"}, changes["sgx.trusted_files"])
}

func TestAppendAndReplace(t *testing.T) {
Expand All @@ -91,11 +97,14 @@ func TestAppendAndReplace(t *testing.T) {
original["sgx.remote_attestation"] = tomlTree.Get("sgx.remote_attestation")
original["sgx.enclave_size"] = tomlTree.Get("sgx.enclave_size")
original["sgx.thread_num"] = tomlTree.Get("sgx.thread_num")
original["sgx.trusted_files"] = tomlTree.Get("sgx.trusted_files")

// Set some changes we want to perform
changes["sgx.remote_attestation"] = 1
changes["sgx.remote_attestation"] = true
changes["sgx.enclave_size"] = "1024M"
changes["sgx.thread_num"] = 16
changedFiles := []interface{}{"file:/usr/favorite.file", "file:/usr/lib/important.so", "file:premain-libos"}
changes["sgx.trusted_files"] = changedFiles

// Calculate the differences
diffs := calculateChanges(original, changes)
Expand All @@ -109,11 +118,13 @@ func TestAppendAndReplace(t *testing.T) {
newTomlTree, err := toml.Load(string(someNewManifest))
assert.NoError(err)
newRemoteAttestation := newTomlTree.Get("sgx.remote_attestation")
assert.EqualValues(1, newRemoteAttestation.(int64))
assert.EqualValues(true, newRemoteAttestation.(bool))
newEnclaveSize := newTomlTree.Get("sgx.enclave_size")
assert.EqualValues("1024M", newEnclaveSize.(string))
newThreadNum := newTomlTree.Get("sgx.thread_num")
assert.EqualValues(16, newThreadNum.(int64))
newTrustedFiles := newTomlTree.Get("sgx.trusted_files")
assert.EqualValues(changedFiles, newTrustedFiles)
}

func TestDownloadPremain(t *testing.T) {
Expand Down
Loading

0 comments on commit c270a92

Please sign in to comment.