Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding enclave restricted flags #1668

Merged
merged 15 commits into from
Dec 5, 2023
Merged
23 changes: 18 additions & 5 deletions dockerfiles/enclave.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
# deploy = copies over only the enclave executable without the source
# in a lightweight base image specialized for deployment and prepares the /data/ folder.

# Final container folder structure:
# /home/obscuro/data contains working files for the enclave
# /home/obscuro/go-obscuro/go/enclave/main contains the executable for the enclave
#

ARG RESTRICTEDMODE
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider providing a default value for the RESTRICTEDMODE argument to ensure the Dockerfile can be built even if the argument is not explicitly provided.

ARG RESTRICTEDMODE=false


FROM ghcr.io/edgelesssys/ego-dev:v1.3.0 AS build-base

# setup container data structure
Expand All @@ -26,13 +33,19 @@ WORKDIR /home/obscuro/go-obscuro/go/enclave/main
RUN --mount=type=cache,target=/root/.cache/go-build \
ego-go build

# New build stage for compiling the enclave with restricted flags mode
FROM build-enclave as build-enclave-restrictedmode-true
# Sign the enclave executable
RUN ego sign main
RUN ego sign enclave.json

# New build stage for compiling the enclave without restricted flags mode
FROM build-enclave as build-enclave-restrictedmode-false
# Sign the enclave executable
RUN ego sign enclave-test.json

# Tag the restricted mode as the current build
FROM build-enclave-restrictedmode-${RESTRICTEDMODE} as build-enclave
otherview marked this conversation as resolved.
Show resolved Hide resolved

# Final container folder structure:
# /home/obscuro/data contains working files for the enclave
# /home/obscuro/go-obscuro/go/enclave/main contains the executable for the enclave
#
# Trigger a new build stage and use the smaller ego version:
FROM ghcr.io/edgelesssys/ego-deploy:v1.3.0
otherview marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
299 changes: 0 additions & 299 deletions go.sum

Large diffs are not rendered by default.

168 changes: 168 additions & 0 deletions go/common/flag/flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package flag

import (
"flag"
"fmt"
"os"
"strconv"
"strings"
)

// WrappedFlag is a construct that allows to have go flags while obeying to a new set of restricteveness rules
type WrappedFlag struct {
flagType string
ptr any
defaultValue any
description string
}

// GetString returns the flag current value casted to string
func (f WrappedFlag) GetString() string {
otherview marked this conversation as resolved.
Show resolved Hide resolved
return *f.ptr.(*string)
}

// GetInt64 returns the flag current value casted to int64
func (f WrappedFlag) GetInt64() int64 {
return *f.ptr.(*int64)
}

// GetBool returns the flag current value casted to bool
func (f WrappedFlag) GetBool() bool {
return *f.ptr.(*bool)
}

// singletonFlagger is the singleton instance of the loaded flags
var singletonFlagger = map[string]*WrappedFlag{}

// String directly uses the go flag package
func String(flagName, defaultValue, description string) *string {
return flag.String(flagName, defaultValue, description)
}

// RestrictedString wraps the go flag package depending on the restriction mode
func RestrictedString(flagName, defaultValue, description string) *WrappedFlag {
prtVal := new(string)
singletonFlagger[flagName] = &WrappedFlag{
flagType: "string",
ptr: prtVal,
defaultValue: defaultValue,
description: description,
}
return singletonFlagger[flagName]
}

// Int64 directly uses the go flag package
func Int64(flagName string, defaultValue int64, description string) *int64 {
return flag.Int64(flagName, defaultValue, description)
}

// RestrictedInt64 wraps the go flag package depending on the restriction mode
func RestrictedInt64(flagName string, defaultValue int64, description string) *WrappedFlag {
prtVal := new(int64)
singletonFlagger[flagName] = &WrappedFlag{
flagType: "int64",
ptr: prtVal,
defaultValue: defaultValue,
description: description,
}
return singletonFlagger[flagName]
}

// Bool directly uses the go flag package
func Bool(flagName string, defaultValue bool, description string) *bool {
return flag.Bool(flagName, defaultValue, description)
}

// RestrictedBool wraps the go flag package depending on the restriction mode
func RestrictedBool(flagName string, defaultValue bool, description string) *WrappedFlag {
prtVal := new(bool)
singletonFlagger[flagName] = &WrappedFlag{
flagType: "bool",
ptr: prtVal,
defaultValue: defaultValue,
description: description,
}
return singletonFlagger[flagName]
}

// Int directly uses the go flag package
func Int(flagName string, defaultValue int, description string) *int {
return flag.Int(flagName, defaultValue, description)
}

// Uint64 directly uses the go flag package
func Uint64(flagName string, defaultValue uint64, description string) *uint64 {
return flag.Uint64(flagName, defaultValue, description)
}

// Parse ensures the restricted mode is applied only to restricted flags
// Restricted Mode - Flags can only be inputted via ENV Vars via the enclave.json
// Non-Restricted Mode - Flags can only be inputted via normal CLI command line
func Parse() error {
mandatoryEnvFlags := false
val := os.Getenv("EDG_RESTRICTED")
if val == "true" {
fmt.Println("Using mandatory signed configurations.")
mandatoryEnvFlags = true
}

for flagName, wflag := range singletonFlagger {
// parse restricted flags if in restricted mode
if mandatoryEnvFlags {
err := parseMandatoryFlags(flagName, wflag)
if err != nil {
return fmt.Errorf("unable to parse mandatory flag: %s - %w", flagName, err)
}
} else {
err := parseNonMandatoryFlag(flagName, wflag)
if err != nil {
return fmt.Errorf("unable to parse flag: %s - %w", flagName, err)
}
}
}

// parse all remaining flags
flag.Parse()
return nil
otherview marked this conversation as resolved.
Show resolved Hide resolved
}

func parseNonMandatoryFlag(flagName string, wflag *WrappedFlag) error {
switch wflag.flagType {
case "string":
wflag.ptr = flag.String(flagName, wflag.defaultValue.(string), wflag.description)
case "int64":
wflag.ptr = flag.Int64(flagName, wflag.defaultValue.(int64), wflag.description)
case "bool":
wflag.ptr = flag.Bool(flagName, wflag.defaultValue.(bool), wflag.description)
default:
return fmt.Errorf("unexpected type: %s", wflag.flagType)
}
return nil
otherview marked this conversation as resolved.
Show resolved Hide resolved
}

func parseMandatoryFlags(flagName string, wflag *WrappedFlag) error {
val := os.Getenv("EDG_" + strings.ToUpper(flagName))
if val == "" {
return fmt.Errorf("mandatory restricted flag not available - %s", flagName)
}

switch wflag.flagType {
case "string":
wflag.ptr = &val
case "int64":
i, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return fmt.Errorf("unable to parse flag %s - %w", flagName, err)
}
wflag.ptr = &i
case "bool":
b, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("unable to parse flag %s - %w", flagName, err)
}
wflag.ptr = &b
default:
return fmt.Errorf("unexpected mandatory type: %s", wflag.flagType)
}
return nil
}
68 changes: 68 additions & 0 deletions go/common/flag/flag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package flag

import (
"fmt"
"os"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestStringFlagCreation(t *testing.T) {
expected := "Not the Default"
flagName := "testString"

// Create the flag
flagOutput := String(flagName, "default", "Test string flag")

// Parse the flag
os.Args = []string{"cmd", fmt.Sprintf("-%s=%s", flagName, expected)}

// Parse the flags
require.NoError(t, Parse())

// Verify the flag value
require.Equal(t, expected, *flagOutput)
}

func TestParseInRestrictedMode(t *testing.T) {
// Set up restricted mode
t.Setenv("EDG_RESTRICTED", "true")
defer os.Unsetenv("EDG_RESTRICTED")

// Create a restricted flag
flagName := "testFlag"
flagOutput := RestrictedString(flagName, "default", "Test restricted flag")

// Mimic setting the environment variable for the restricted flag
expectedValue := "restrictedValue"
t.Setenv("EDG_"+strings.ToUpper(flagName), expectedValue)

defer os.Unsetenv("EDG_" + strings.ToUpper(flagName))

// Parse the flags
require.NoError(t, Parse())

// Verify the flag value
require.Equal(t, expectedValue, flagOutput.GetString())
}

func TestParseInUnrestrictedMode(t *testing.T) {
// Ensure unrestricted mode
os.Unsetenv("EDG_RESTRICTED")

// Create a regular flag
flagName := "testUnrestrictedFlag"
expected := int64(12345)
// Parse the flag
os.Args = []string{"cmd", fmt.Sprintf("-%s=%d", flagName, expected)}

flagOutput := RestrictedInt64(flagName, int64(1), "Test flag")

// Parse the flags
require.NoError(t, Parse())

// Verify the flag value
require.Equal(t, expected, flagOutput.GetInt64())
otherview marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading