From 08736b9eba7ee6ce51139fa242bb015f7aa550f1 Mon Sep 17 00:00:00 2001 From: Thomas Tendyck Date: Mon, 7 Oct 2024 13:44:20 +0000 Subject: [PATCH 1/4] coordinator: add Marble type as extension to Marble certificate --- coordinator/core/marbleapi.go | 7 +++++-- coordinator/oid/oid.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 coordinator/oid/oid.go diff --git a/coordinator/core/marbleapi.go b/coordinator/core/marbleapi.go index e2fc7082..2fa4d2a1 100644 --- a/coordinator/core/marbleapi.go +++ b/coordinator/core/marbleapi.go @@ -13,6 +13,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/x509" + "crypto/x509/pkix" "encoding/json" "encoding/pem" "errors" @@ -24,6 +25,7 @@ import ( "github.com/edgelesssys/ego/marble" "github.com/edgelesssys/marblerun/coordinator/constants" "github.com/edgelesssys/marblerun/coordinator/manifest" + "github.com/edgelesssys/marblerun/coordinator/oid" "github.com/edgelesssys/marblerun/coordinator/quote" "github.com/edgelesssys/marblerun/coordinator/rpc" "github.com/edgelesssys/marblerun/coordinator/state" @@ -251,7 +253,7 @@ func (c *Core) verifyManifestRequirement(txdata storeGetter, tlsCert *x509.Certi } // generateCertFromCSR signs the CSR from marble attempting to register. -func (c *Core) generateCertFromCSR(txdata storeGetter, csrReq []byte, pubk ecdsa.PublicKey, marbleUUID string) ([]byte, error) { +func (c *Core) generateCertFromCSR(txdata storeGetter, csrReq []byte, pubk ecdsa.PublicKey, marbleType string, marbleUUID string) ([]byte, error) { // parse and verify CSR csr, err := x509.ParseCertificateRequest(csrReq) if err != nil { @@ -288,6 +290,7 @@ func (c *Core) generateCertFromCSR(txdata storeGetter, csrReq []byte, pubk ecdsa NotAfter: notAfter, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte(marbleType)}}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, IsCA: false, @@ -399,7 +402,7 @@ func (c *Core) generateMarbleAuthSecrets(txdata storeGetter, req *rpc.Activation } // Generate Marble certificate - certRaw, err := c.generateCertFromCSR(txdata, req.GetCSR(), privk.PublicKey, marbleUUID.String()) + certRaw, err := c.generateCertFromCSR(txdata, req.GetCSR(), privk.PublicKey, req.GetMarbleType(), marbleUUID.String()) if err != nil { return reservedSecrets{}, err } diff --git a/coordinator/oid/oid.go b/coordinator/oid/oid.go new file mode 100644 index 00000000..8f767d44 --- /dev/null +++ b/coordinator/oid/oid.go @@ -0,0 +1,12 @@ +// Copyright (c) Edgeless Systems GmbH. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package oid + +import "encoding/asn1" + +// MarbleType is the OID for the Marble type in the Marble certificate. +var MarbleType = asn1.ObjectIdentifier{1, 3, 9903, 2, 1} From 2392fe36eb0183af34ea54b98d8b031fed7072fe Mon Sep 17 00:00:00 2001 From: Thomas Tendyck Date: Mon, 7 Oct 2024 13:46:05 +0000 Subject: [PATCH 2/4] coordinator: add monotonic counter --- coordinator/clientapi/clientapi.go | 93 +++++++++ coordinator/clientapi/clientapi_test.go | 252 ++++++++++++++++++++++++ coordinator/manifest/manifest.go | 7 +- coordinator/server/handler/handler.go | 16 ++ coordinator/server/server.go | 1 + coordinator/server/server_test.go | 92 +++++++++ coordinator/server/v2/types.go | 11 ++ coordinator/server/v2/v2.go | 30 +++ coordinator/store/request/request.go | 1 + coordinator/store/wrapper/wrapper.go | 21 ++ docs/docs/workflows/define-manifest.md | 1 + 11 files changed, 524 insertions(+), 1 deletion(-) diff --git a/coordinator/clientapi/clientapi.go b/coordinator/clientapi/clientapi.go index f957137c..f13d9bc3 100644 --- a/coordinator/clientapi/clientapi.go +++ b/coordinator/clientapi/clientapi.go @@ -26,6 +26,7 @@ import ( "github.com/edgelesssys/marblerun/coordinator/constants" "github.com/edgelesssys/marblerun/coordinator/crypto" "github.com/edgelesssys/marblerun/coordinator/manifest" + "github.com/edgelesssys/marblerun/coordinator/oid" "github.com/edgelesssys/marblerun/coordinator/quote" "github.com/edgelesssys/marblerun/coordinator/recovery" "github.com/edgelesssys/marblerun/coordinator/seal" @@ -506,6 +507,43 @@ func (a *ClientAPI) SetManifest(ctx context.Context, rawManifest []byte) (recove return recoverySecretMap, nil } +// SetMonotonicCounter sets the new value (if greater) of the counter identified by marbleType, marbleUUID, and name. +func (a *ClientAPI) SetMonotonicCounter(ctx context.Context, marbleType string, marbleUUID uuid.UUID, name string, value uint64) (prevValue uint64, err error) { + friendlyID := zap.String("id", fmt.Sprintf("%s:%s:%s", marbleType, marbleUUID, name)) + + a.log.Info("SetMonotonicCounter called", friendlyID, zap.Uint64("value", value)) + defer a.core.Unlock() + if err := a.core.RequireState(ctx, state.AcceptingMarbles); err != nil { + a.log.Error("SetMonotonicCounter: Coordinator not in correct state", zap.Error(err)) + return 0, err + } + defer func() { + if err != nil { + a.log.Error("SetMonotonicCounter failed", zap.Error(err), friendlyID) + } + }() + + counterID := encodeMonotonicCounterID(marbleType, marbleUUID, name) + + txdata, rollback, commit, err := wrapper.WrapTransaction(ctx, a.txHandle) + if err != nil { + return 0, err + } + defer rollback() + + prevValue, err = txdata.SetMonotonicCounter(counterID, value) + if err != nil { + return 0, err + } + + if err := commit(ctx); err != nil { + return 0, fmt.Errorf("committing store transaction: %w", err) + } + + a.log.Info("SetMonotonicCounter successful", friendlyID) + return prevValue, nil +} + // UpdateManifest allows to update certain package parameters of the original manifest, supplied via a JSON manifest. func (a *ClientAPI) UpdateManifest(ctx context.Context, rawUpdateManifest []byte, updater *user.User) (err error) { a.log.Info("UpdateManifest called") @@ -650,6 +688,55 @@ func (a *ClientAPI) UpdateManifest(ctx context.Context, rawUpdateManifest []byte return nil } +// VerifyMarble checks if a given client certificate is a Marble certificate signed by this Coordinator. +func (a *ClientAPI) VerifyMarble(ctx context.Context, clientCerts []*x509.Certificate) (string, uuid.UUID, error) { + defer a.core.Unlock() + if err := a.core.RequireState(ctx, state.AcceptingMarbles); err != nil { + a.log.Error("VerifyMarble: Coordinator not in correct state", zap.Error(err)) + return "", uuid.UUID{}, err + } + + txdata, rollback, _, err := wrapper.WrapTransaction(ctx, a.txHandle) + if err != nil { + return "", uuid.UUID{}, err + } + defer rollback() + + marbleRootCert, err := txdata.GetCertificate(constants.SKMarbleRootCert) + if err != nil { + return "", uuid.UUID{}, fmt.Errorf("getting Marble root certificate: %w", err) + } + verifyOpts := x509.VerifyOptions{Roots: x509.NewCertPool()} + verifyOpts.Roots.AddCert(marbleRootCert) + + // Check if a supplied client cert was signed by the Marble root cert and get the type and UUID + var errs error + for _, suppliedCert := range clientCerts { + if _, err := suppliedCert.Verify(verifyOpts); err != nil { + errs = errors.Join(errs, err) + continue + } + + marbleUUID, err := uuid.Parse(suppliedCert.Subject.CommonName) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + for _, ex := range suppliedCert.Extensions { + if ex.Id.Equal(oid.MarbleType) { + return string(ex.Value), marbleUUID, nil + } + } + errs = errors.Join(errs, errors.New("MarbleType extension not found in certificate")) + } + + if errs == nil { + errs = errors.New("client certificate did not match any Marble") + } + return "", uuid.UUID{}, errs +} + // VerifyUser checks if a given client certificate matches the admin certificates specified in the manifest. func (a *ClientAPI) VerifyUser(ctx context.Context, clientCerts []*x509.Certificate) (*user.User, error) { txdata, rollback, _, err := wrapper.WrapTransaction(ctx, a.txHandle) @@ -822,3 +909,9 @@ func (a *ClientAPI) FeatureEnabled(ctx context.Context, feature string) bool { return strings.EqualFold(s, feature) }) } + +func encodeMonotonicCounterID(marbleType string, marbleUUID uuid.UUID, name string) string { + // use unambiguous concatenation as counter ID + b64 := base64.StdEncoding.EncodeToString + return fmt.Sprintf("%s:%s:%s", b64([]byte(marbleType)), b64(marbleUUID[:]), b64([]byte(name))) +} diff --git a/coordinator/clientapi/clientapi_test.go b/coordinator/clientapi/clientapi_test.go index 06529695..58913b61 100644 --- a/coordinator/clientapi/clientapi_test.go +++ b/coordinator/clientapi/clientapi_test.go @@ -10,20 +10,26 @@ import ( "bytes" "context" "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" "crypto/sha256" "crypto/x509" + "crypto/x509/pkix" "encoding/base64" "encoding/binary" "encoding/json" "encoding/pem" "errors" + "math/big" "testing" + "time" "github.com/edgelesssys/ego/attestation" "github.com/edgelesssys/ego/attestation/tcbstatus" "github.com/edgelesssys/marblerun/coordinator/constants" "github.com/edgelesssys/marblerun/coordinator/crypto" "github.com/edgelesssys/marblerun/coordinator/manifest" + "github.com/edgelesssys/marblerun/coordinator/oid" "github.com/edgelesssys/marblerun/coordinator/seal" "github.com/edgelesssys/marblerun/coordinator/state" "github.com/edgelesssys/marblerun/coordinator/store" @@ -707,6 +713,122 @@ func TestSetManifest(t *testing.T) { } } +func TestSetMonotonicCounter(t *testing.T) { + const defaultType = "type" + defaultUUID := uuid.UUID{2, 3, 4} + const defaultName = "name" + defaultID := encodeMonotonicCounterID(defaultType, defaultUUID, defaultName) + defaultKey := "monotonicCounter:" + defaultID + + testCases := map[string]struct { + coreState state.State + store fakeStoreTransaction + marbleType string + marbleUUID uuid.UUID + name string + value uint64 + wantValue uint64 + wantStoreValue []byte + wantErr bool + }{ + "new value is smaller": { + coreState: state.AcceptingMarbles, + store: fakeStoreTransaction{ + state: map[string][]byte{defaultKey: {3, 0, 0, 0, 0, 0, 0, 0}}, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 2, + wantValue: 3, + wantStoreValue: []byte{3, 0, 0, 0, 0, 0, 0, 0}, + }, + "new value is equal": { + coreState: state.AcceptingMarbles, + store: fakeStoreTransaction{ + state: map[string][]byte{defaultKey: {3, 0, 0, 0, 0, 0, 0, 0}}, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 3, + wantValue: 3, + wantStoreValue: []byte{3, 0, 0, 0, 0, 0, 0, 0}, + }, + "new value is greater": { + coreState: state.AcceptingMarbles, + store: fakeStoreTransaction{ + state: map[string][]byte{defaultKey: {3, 0, 0, 0, 0, 0, 0, 0}}, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 4, + wantValue: 3, + wantStoreValue: []byte{4, 0, 0, 0, 0, 0, 0, 0}, + }, + "wrong core state": { + coreState: state.AcceptingManifest, + store: fakeStoreTransaction{ + state: map[string][]byte{defaultKey: {3, 0, 0, 0, 0, 0, 0, 0}}, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 4, + wantErr: true, + }, + "counter not set yet": { + coreState: state.AcceptingMarbles, + store: fakeStoreTransaction{ + state: map[string][]byte{}, + getErr: store.ErrValueUnset, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 4, + wantValue: 0, + wantStoreValue: []byte{4, 0, 0, 0, 0, 0, 0, 0}, + }, + "store error": { + coreState: state.AcceptingMarbles, + store: fakeStoreTransaction{ + state: map[string][]byte{defaultKey: {3, 0, 0, 0, 0, 0, 0, 0}}, + getErr: assert.AnError, + }, + marbleType: defaultType, + marbleUUID: defaultUUID, + name: defaultName, + value: 4, + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + api := ClientAPI{ + txHandle: &tc.store, + core: &fakeCore{state: tc.coreState}, + log: zaptest.NewLogger(t), + } + + gotValue, err := api.SetMonotonicCounter(context.Background(), tc.marbleType, tc.marbleUUID, tc.name, tc.value) + if tc.wantErr { + assert.Error(err) + return + } + require.NoError(err) + + assert.Equal(tc.wantValue, gotValue) + assert.Equal(tc.wantStoreValue, tc.store.state[defaultKey]) + }) + } +} + func TestSignQuote(t *testing.T) { testCases := map[string]struct { store *fakeStoreTransaction @@ -864,6 +986,136 @@ func TestUpdateManifest(t *testing.T) { t.Log("WARNING: Missing unit Test for UpdateManifest") } +func TestVerifyMarble(t *testing.T) { + marbleRootCert, marbleRootKey, err := crypto.GenerateCert(nil, "MarbleRun Unit Test Marble", nil, nil, nil) + require.NoError(t, err) + otherRootCert, otherRootKey, err := crypto.GenerateCert(nil, "MarbleRun Unit Test Marble", nil, nil, nil) + require.NoError(t, err) + + createCert := func(template *x509.Certificate, rootCert *x509.Certificate, rootKey *ecdsa.PrivateKey) *x509.Certificate { + marblePubKey := &rsa.PublicKey{N: big.NewInt(1), E: 1} + certRaw, err := x509.CreateCertificate(rand.Reader, template, rootCert, marblePubKey, rootKey) + require.NoError(t, err) + cert, err := x509.ParseCertificate(certRaw) + require.NoError(t, err) + return cert + } + + testCases := map[string]struct { + coreState state.State + clientCerts []*x509.Certificate + wantType string + wantUUID uuid.UUID + wantErr bool + }{ + "success": { + coreState: state.AcceptingMarbles, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030400-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("type")}}, + }, marbleRootCert, marbleRootKey), + }, + wantType: "type", + wantUUID: uuid.UUID{2, 3, 4}, + }, + "wrong core state": { + coreState: state.AcceptingManifest, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030400-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("type")}}, + }, marbleRootCert, marbleRootKey), + }, + wantErr: true, + }, + "invalid signer": { + coreState: state.AcceptingMarbles, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030400-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("type")}}, + }, otherRootCert, otherRootKey), + }, + wantErr: true, + }, + "invalid CN": { + coreState: state.AcceptingMarbles, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "foo"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("type")}}, + }, marbleRootCert, marbleRootKey), + }, + wantErr: true, + }, + "missing Marble type": { + coreState: state.AcceptingMarbles, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030400-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + }, marbleRootCert, marbleRootKey), + }, + wantErr: true, + }, + "multiple certificates": { + coreState: state.AcceptingMarbles, + clientCerts: []*x509.Certificate{ + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030500-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("other")}}, + }, otherRootCert, otherRootKey), + createCert(&x509.Certificate{ + SerialNumber: &big.Int{}, + Subject: pkix.Name{CommonName: "02030400-0000-0000-0000-000000000000"}, + NotAfter: time.Now().Add(time.Hour), + ExtraExtensions: []pkix.Extension{{Id: oid.MarbleType, Value: []byte("type")}}, + }, marbleRootCert, marbleRootKey), + }, + wantType: "type", + wantUUID: uuid.UUID{2, 3, 4}, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + // prepare store and API + store := &fakeStoreTransaction{state: map[string][]byte{}} + require.NoError(wrapper.New(store).PutCertificate(constants.SKMarbleRootCert, marbleRootCert)) + api := ClientAPI{ + txHandle: store, + core: &fakeCore{state: tc.coreState}, + log: zaptest.NewLogger(t), + } + + marbleType, marbleUUID, err := api.VerifyMarble(context.Background(), tc.clientCerts) + if tc.wantErr { + assert.Error(err) + return + } + require.NoError(err) + + assert.Equal(tc.wantType, marbleType) + assert.Equal(tc.wantUUID, marbleUUID) + }) + } +} + func TestVerifyUser(t *testing.T) { t.Log("WARNING: Missing unit Test for VerifyUser") } diff --git a/coordinator/manifest/manifest.go b/coordinator/manifest/manifest.go index 0afc2749..7743da07 100644 --- a/coordinator/manifest/manifest.go +++ b/coordinator/manifest/manifest.go @@ -41,6 +41,9 @@ const ( // FeatureSignQuoteEndpoint enables the /sign-quote endpoint. // This endpoint allows to verify an SGX quote and sign the result with the Coordinator's private key. FeatureSignQuoteEndpoint = "SignQuoteEndpoint" + + // FeatureMonotonicCounter enables the monotonic counter feature and the /monotonic-counter endpoint. + FeatureMonotonicCounter = "MonotonicCounter" ) // Manifest defines the rules of a MarbleRun deployment. @@ -511,7 +514,9 @@ func (m Manifest) Check(zaplogger *zap.Logger) error { } for _, feature := range m.Config.FeatureGates { - if feature != FeatureSignQuoteEndpoint { + switch feature { + case FeatureSignQuoteEndpoint, FeatureMonotonicCounter: + default: return fmt.Errorf("unknown feature gate: %s", feature) } } diff --git a/coordinator/server/handler/handler.go b/coordinator/server/handler/handler.go index fafd8a0b..0ec0a065 100644 --- a/coordinator/server/handler/handler.go +++ b/coordinator/server/handler/handler.go @@ -17,6 +17,7 @@ import ( "github.com/edgelesssys/marblerun/coordinator/manifest" "github.com/edgelesssys/marblerun/coordinator/state" "github.com/edgelesssys/marblerun/coordinator/user" + "github.com/google/uuid" ) // ClientAPI is the interface implementing the backend logic of the REST API. @@ -28,7 +29,9 @@ type ClientAPI interface { GetStatus(context.Context) (statusCode state.State, status string, err error) GetUpdateLog(context.Context) (updateLog []string, err error) Recover(ctx context.Context, encryptionKey []byte) (int, error) + SetMonotonicCounter(ctx context.Context, marbleType string, marbleUUID uuid.UUID, name string, value uint64) (uint64, error) SignQuote(ctx context.Context, quote []byte) (signature []byte, tcbStatus string, err error) + VerifyMarble(ctx context.Context, clientCerts []*x509.Certificate) (string, uuid.UUID, error) VerifyUser(ctx context.Context, clientCerts []*x509.Certificate) (*user.User, error) UpdateManifest(ctx context.Context, rawUpdateManifest []byte, updater *user.User) error WriteSecrets(ctx context.Context, secrets map[string]manifest.UserSecret, updater *user.User) error @@ -56,6 +59,19 @@ func GetPost(getHandler, postHandler func(http.ResponseWriter, *http.Request)) f } } +// VerifyMarble checks if the Marble is authorized to access the API. +func VerifyMarble(verifyFunc func(context.Context, []*x509.Certificate) (string, uuid.UUID, error), r *http.Request) (string, uuid.UUID, error) { + // Abort if no client certificate was provided + if r.TLS == nil { + return "", uuid.UUID{}, errors.New("no client certificate provided") + } + marbleType, marbleUUID, err := verifyFunc(r.Context(), r.TLS.PeerCertificates) + if err != nil { + return "", uuid.UUID{}, fmt.Errorf("unauthorized marble: %w", err) + } + return marbleType, marbleUUID, nil +} + // VerifyUser checks if the user is authorized to access the API. func VerifyUser(verifyFunc func(context.Context, []*x509.Certificate) (*user.User, error), r *http.Request) (*user.User, error) { // Abort if no user client certificate was provided diff --git a/coordinator/server/server.go b/coordinator/server/server.go index abbad57a..bef3d919 100644 --- a/coordinator/server/server.go +++ b/coordinator/server/server.go @@ -105,6 +105,7 @@ func CreateServeMux(api handler.ClientAPI, promFactory *promauto.Factory, log *z router.HandleFunc(v2Endpoint+"/quote", handler.GetPost(serverV2.QuoteGet, handler.MethodNotAllowedHandler)) router.HandleFunc(v2Endpoint+"/recover", handler.GetPost(handler.MethodNotAllowedHandler, serverV2.RecoverPost)) router.HandleFunc(v2Endpoint+"/sign-quote", handler.GetPost(handler.MethodNotAllowedHandler, serverV2.SignQuotePost)) + router.HandleFunc(v2Endpoint+"/monotonic-counter", handler.GetPost(handler.MethodNotAllowedHandler, serverV2.MonotonicCounterPost)) return router } diff --git a/coordinator/server/server_test.go b/coordinator/server/server_test.go index 69623e0d..439a22aa 100644 --- a/coordinator/server/server_test.go +++ b/coordinator/server/server_test.go @@ -15,15 +15,18 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "net/http" "net/http/httptest" "strings" "sync" "testing" + "github.com/edgelesssys/marblerun/coordinator/server/handler" v1 "github.com/edgelesssys/marblerun/coordinator/server/v1" "github.com/edgelesssys/marblerun/test" "github.com/edgelesssys/marblerun/util" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" @@ -114,6 +117,65 @@ func TestGetUpdateLog(t *testing.T) { assert.EqualValues('{', resp.Body.String()[0]) } +func TestMonotonicCounter(t *testing.T) { + testCases := map[string]struct { + api stubAPI + req string + wantStatus int + wantOldValue uint64 + wantID string + wantNewValue uint64 + }{ + "success": { + api: stubAPI{featureEnabledResult: true}, + req: `{"name":"foo","value":3}`, + wantStatus: http.StatusOK, + wantOldValue: 2, + wantID: "type:02030400-0000-0000-0000-000000000000:foo", + wantNewValue: 3, + }, + "bad request": { + api: stubAPI{featureEnabledResult: true}, + req: "bad", + wantStatus: http.StatusBadRequest, + }, + "feature not enabled": { + api: stubAPI{featureEnabledResult: false}, + req: `{"name":"foo","value":3}`, + wantStatus: http.StatusForbidden, + }, + "VerifyMarble error": { + api: stubAPI{featureEnabledResult: true, verifyMarbleErr: assert.AnError}, + req: `{"name":"foo","value":3}`, + wantStatus: http.StatusUnauthorized, + }, + "SetMonotonicCounter error": { + api: stubAPI{featureEnabledResult: true, setMonotonicCounterErr: assert.AnError}, + req: `{"name":"foo","value":3}`, + wantStatus: http.StatusInternalServerError, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + mux := CreateServeMux(&tc.api, nil, nil) + + req := httptest.NewRequest(http.MethodPost, "/api/v2/monotonic-counter", strings.NewReader(tc.req)) + req.TLS = &tls.ConnectionState{} + + resp := httptest.NewRecorder() + mux.ServeHTTP(resp, req) + + assert.Equal(tc.wantStatus, resp.Code) + assert.Equal(tc.wantOldValue, gjson.Get(resp.Body.String(), "data.value").Uint()) + assert.Equal(tc.wantID, tc.api.setMonotonicCounterID) + assert.Equal(tc.wantNewValue, tc.api.setMonotonicCounterValue) + }) + } +} + func TestUpdate(t *testing.T) { assert := assert.New(t) require := require.New(t) @@ -235,3 +297,33 @@ func TestConcurrent(t *testing.T) { go postManifest() wg.Wait() } + +type stubAPI struct { + handler.ClientAPI + + featureEnabledResult bool + verifyMarbleErr error + setMonotonicCounterErr error + + featureEnabledFeature string + setMonotonicCounterID string + setMonotonicCounterValue uint64 +} + +func (a *stubAPI) FeatureEnabled(_ context.Context, feature string) bool { + a.featureEnabledFeature = feature + return a.featureEnabledResult +} + +func (a *stubAPI) VerifyMarble(_ context.Context, _ []*x509.Certificate) (string, uuid.UUID, error) { + return "type", uuid.UUID{2, 3, 4}, a.verifyMarbleErr +} + +func (a *stubAPI) SetMonotonicCounter(_ context.Context, marbleType string, marbleUUID uuid.UUID, name string, value uint64) (uint64, error) { + if a.setMonotonicCounterErr != nil { + return 0, a.setMonotonicCounterErr + } + a.setMonotonicCounterID = fmt.Sprintf("%v:%v:%v", marbleType, marbleUUID, name) + a.setMonotonicCounterValue = value + return 2, nil +} diff --git a/coordinator/server/v2/types.go b/coordinator/server/v2/types.go index f4e2357b..3393814d 100644 --- a/coordinator/server/v2/types.go +++ b/coordinator/server/v2/types.go @@ -43,6 +43,17 @@ type ManifestSetResponse struct { RecoverySecrets map[string][]byte } +// MonotonicCounterRequest is the request structure for setting a monotonic counter. +type MonotonicCounterRequest struct { + Name string `json:"name"` + Value uint64 `json:"value"` +} + +// MonotonicCounterResponse contains the response to setting a monotonic counter. +type MonotonicCounterResponse struct { + Value uint64 `json:"value"` +} + // QuoteSignRequest contains an SGX Quote to be verified and signed by the Coordinator. type QuoteSignRequest struct { // SGXQuote is the raw SGX quote data. diff --git a/coordinator/server/v2/v2.go b/coordinator/server/v2/v2.go index d072e724..1ee141f0 100644 --- a/coordinator/server/v2/v2.go +++ b/coordinator/server/v2/v2.go @@ -67,6 +67,36 @@ func (s *ClientAPIServer) ManifestPost(w http.ResponseWriter, r *http.Request) { handler.WriteJSON(w, ManifestSetResponse{RecoverySecrets: recoverySecretMap}) } +// MonotonicCounterPost increments a monotonic counter of the Coordinator. +// The requesting Marble must be authorized to increment the counter. +func (s *ClientAPIServer) MonotonicCounterPost(w http.ResponseWriter, r *http.Request) { + // Check if the current manifest allows this feature + if !s.api.FeatureEnabled(r.Context(), manifest.FeatureMonotonicCounter) { + handler.WriteJSONError(w, "MonotonicCounter feature is not enabled in the manifest", http.StatusForbidden) + return + } + + marbleType, marbleUUID, err := handler.VerifyMarble(s.api.VerifyMarble, r) + if err != nil { + handler.WriteJSONFailure(w, nil, err.Error(), http.StatusUnauthorized) + return + } + + var req MonotonicCounterRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + handler.WriteJSONFailure(w, nil, err.Error(), http.StatusBadRequest) + return + } + + value, err := s.api.SetMonotonicCounter(r.Context(), marbleType, marbleUUID, req.Name, req.Value) + if err != nil { + handler.WriteJSONError(w, err.Error(), http.StatusInternalServerError) + return + } + + handler.WriteJSON(w, MonotonicCounterResponse{Value: value}) +} + // QuoteGet retrieves a remote attestation quote and certificates. // By default, the Coordinator will return a pre-generated quote over the root certificate of the TLS connection. // If a nonce is supplied as a query parameter, a new quote will be generated over sha256(root_cert || nonce). diff --git a/coordinator/store/request/request.go b/coordinator/store/request/request.go index 0cff433d..8337cbb8 100644 --- a/coordinator/store/request/request.go +++ b/coordinator/store/request/request.go @@ -15,6 +15,7 @@ const ( Manifest = "manifest" ManifestSignature = "manifestSignature" Marble = "marble" + MonotonicCounter = "monotonicCounter" Package = "package" PrivateKey = "privateKey" Secret = "secret" diff --git a/coordinator/store/wrapper/wrapper.go b/coordinator/store/wrapper/wrapper.go index bda77390..0c54d63c 100644 --- a/coordinator/store/wrapper/wrapper.go +++ b/coordinator/store/wrapper/wrapper.go @@ -10,6 +10,7 @@ import ( "context" "crypto/ecdsa" "crypto/x509" + "encoding/binary" "encoding/json" "errors" "strconv" @@ -285,6 +286,26 @@ func (s Wrapper) PutUser(newUser *user.User) error { return s.put(request.User, newUser.Name(), newUser) } +// SetMonotonicCounter increases the value of a monotonic counter in the store and returns the previous value. +func (s Wrapper) SetMonotonicCounter(name string, value uint64) (uint64, error) { + request := request.MonotonicCounter + ":" + name + + var currentValue uint64 + if raw, err := s.store.Get(request); err == nil { + currentValue = binary.LittleEndian.Uint64(raw) + } else if !errors.Is(err, store.ErrValueUnset) { + return 0, err + } + + if value > currentValue { + if err := s.store.Put(request, binary.LittleEndian.AppendUint64(nil, value)); err != nil { + return 0, err + } + } + + return currentValue, nil +} + // put is the default method for marshaling and saving data to store. func (s Wrapper) put(requestType, requestResource string, target interface{}) error { request := strings.Join([]string{requestType, requestResource}, ":") diff --git a/docs/docs/workflows/define-manifest.md b/docs/docs/workflows/define-manifest.md index 8cb7f528..ca5fbad0 100644 --- a/docs/docs/workflows/define-manifest.md +++ b/docs/docs/workflows/define-manifest.md @@ -537,3 +537,4 @@ See the section on [seal key types](../architecture/security.md#seal-key) for mo `FeatureGates` allows you to opt-in to additional features that may be useful for certain use cases. The following features are available: * `SignQuoteEndpoint`: enables the [sign-quote endpoint](../reference/coordinator.md#verify-and-sign-an-sgx-quote) +* `MonotonicCounter`: enables the [monotonic counter feature](https://github.com/edgelesssys/marblerun/tree/master/samples/estore) From 54b3afc07e7bd6eeb34f1026df69fd4aa9a32001 Mon Sep 17 00:00:00 2001 From: Thomas Tendyck Date: Mon, 7 Oct 2024 13:47:42 +0000 Subject: [PATCH 3/4] api: add SetMonotonicCounter --- api/api.go | 77 +++++++++++++++++++++++++++++++++ api/rest/rest.go | 25 ++++++----- cmd/marble-test/main.go | 42 ++++++++++++++++-- coordinator/core/marbleapi.go | 20 +++++++-- internal/constants/constants.go | 9 ++++ test/framework/framework.go | 1 + test/integration_test.go | 18 ++++++++ test/manifests.go | 9 ++++ 8 files changed, 182 insertions(+), 19 deletions(-) diff --git a/api/api.go b/api/api.go index 26f9194d..22cd4cd1 100644 --- a/api/api.go +++ b/api/api.go @@ -17,16 +17,19 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "encoding/pem" "errors" "fmt" "io" "net/http" + "os" "github.com/edgelesssys/ego/attestation/tcbstatus" "github.com/edgelesssys/marblerun/api/attestation" "github.com/edgelesssys/marblerun/api/rest" "github.com/edgelesssys/marblerun/coordinator/manifest" apiv2 "github.com/edgelesssys/marblerun/coordinator/server/v2" + "github.com/edgelesssys/marblerun/internal/constants" "github.com/edgelesssys/marblerun/util" "github.com/spf13/afero" ) @@ -385,6 +388,41 @@ func SecretSet(ctx context.Context, endpoint string, trustedRoot *x509.Certifica return nil } +// SetMonotonicCounter increases a monotonic counter managed by the Coordinator. +// +// This function can only be called by a Marble. The counter is bound to the Marble's type and UUID. +// +// If the passed value is greater than the counter's value, it is set as the new value and the old value is returned. +// Otherwise, the value is not changed and the current value is returned. +func SetMonotonicCounter(ctx context.Context, endpoint string, name string, value uint64) (uint64, error) { + marbleKeyPair, trustedRoot, err := getMarbleCredentialsFromEnv() + if err != nil { + return 0, fmt.Errorf("getting credentials from secure environment: %w", err) + } + + client, err := rest.NewClient(endpoint, trustedRoot, &marbleKeyPair) + if err != nil { + return 0, fmt.Errorf("setting up client: %w", err) + } + + reqBody, err := json.Marshal(apiv2.MonotonicCounterRequest{Name: name, Value: value}) + if err != nil { + return 0, fmt.Errorf("marshalling request: %w", err) + } + + resp, err := client.Post(ctx, rest.V2API+rest.MonotonicCounterEndpoint, rest.ContentJSON, bytes.NewReader(reqBody)) + if err != nil { + return 0, fmt.Errorf("sending request: %w", err) + } + + var response apiv2.MonotonicCounterResponse + if err := json.Unmarshal(resp, &response); err != nil { + return 0, fmt.Errorf("unmarshalling Coordinator response: %w", err) + } + + return response.Value, nil +} + // SignQuote sends an SGX quote to a Coordinator for signing. // If the quote is valid, the Coordinator will sign the quote using its root ECDSA key, and return the signature with the TCB status of the quote. // The Coordinator does not verify if the quote matches any packages in the configured manifest. @@ -500,3 +538,42 @@ func verifyOptionsFromConfig(fs afero.Afero, configPath string) (VerifyOptions, err = json.Unmarshal(optsRaw, &opts) return opts, err } + +func getByteEnv(name string) ([]byte, error) { + value := os.Getenv(name) + if len(value) == 0 { + return nil, fmt.Errorf("environment variable not set: %s", name) + } + return []byte(value), nil +} + +func getMarbleCredentialsFromEnv() (tls.Certificate, *x509.Certificate, error) { + certChain, err := getByteEnv(constants.MarbleEnvironmentCertificateChain) + if err != nil { + return tls.Certificate{}, nil, err + } + rootCA, err := getByteEnv(constants.MarbleEnvironmentCoordinatorRootCA) + if err != nil { + return tls.Certificate{}, nil, err + } + leafPrivk, err := getByteEnv(constants.MarbleEnvironmentPrivateKey) + if err != nil { + return tls.Certificate{}, nil, err + } + + block, _ := pem.Decode(rootCA) + if block == nil { + return tls.Certificate{}, nil, errors.New("decoding Coordinator root certificate failed") + } + coordinatorRoot, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return tls.Certificate{}, nil, fmt.Errorf("parsing Coordinator root certificate: %w", err) + } + + tlsCert, err := tls.X509KeyPair(certChain, leafPrivk) + if err != nil { + return tls.Certificate{}, nil, fmt.Errorf("creating TLS key pair: %w", err) + } + + return tlsCert, coordinatorRoot, nil +} diff --git a/api/rest/rest.go b/api/rest/rest.go index d1e24660..23b0ccb8 100644 --- a/api/rest/rest.go +++ b/api/rest/rest.go @@ -23,18 +23,19 @@ import ( // Endpoints of the MarbleRun Coordinator REST API. const ( - ManifestEndpoint = "manifest" - UpdateEndpoint = "update" - UpdateCancelEndpoint = "update-cancel" - UpdateStatusEndpoint = "update-manifest" - QuoteEndpoint = "quote" - RecoverEndpoint = "recover" - SecretEndpoint = "secrets" - StatusEndpoint = "status" - SignQuoteEndpoint = "sign-quote" - V2API = "/api/v2/" - ContentJSON = "application/json" - ContentPlain = "text/plain" + ManifestEndpoint = "manifest" + UpdateEndpoint = "update" + UpdateCancelEndpoint = "update-cancel" + UpdateStatusEndpoint = "update-manifest" + QuoteEndpoint = "quote" + RecoverEndpoint = "recover" + SecretEndpoint = "secrets" + StatusEndpoint = "status" + SignQuoteEndpoint = "sign-quote" + MonotonicCounterEndpoint = "monotonic-counter" + V2API = "/api/v2/" + ContentJSON = "application/json" + ContentPlain = "text/plain" ) const ( diff --git a/cmd/marble-test/main.go b/cmd/marble-test/main.go index 5900c271..db5fc0c2 100644 --- a/cmd/marble-test/main.go +++ b/cmd/marble-test/main.go @@ -13,17 +13,27 @@ import ( "net/http" "net/url" "os" + "time" "github.com/edgelesssys/ego/marble" + "github.com/edgelesssys/marblerun/api" "github.com/edgelesssys/marblerun/util" ) func main() { addr := util.MustGetenv("EDG_TEST_ADDR") - if len(os.Args) > 1 && os.Args[1] == "serve" { - runServer(addr) - return + if len(os.Args) > 1 { + switch os.Args[1] { + case "serve": + runServer(addr) + return + case "monotonic-counter": + if err := testMonotonicCounter(); err != nil { + log.Fatal(err) + } + return + } } if err := runClient(addr); err != nil { @@ -81,3 +91,29 @@ func runClient(addr string) error { log.Printf("Successful connection to Server: %v", resp.Status) return nil } + +func testMonotonicCounter() error { + const counterName = "foo" + endpoint := os.Getenv("EDG_COORDINATOR_CLIENT_ADDR") + + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + value, err := api.SetMonotonicCounter(ctx, endpoint, counterName, 2) + if err != nil { + return fmt.Errorf("first call to SetMonotonicCounter: %w", err) + } + if value != 0 { + return fmt.Errorf("expected initial value 0, got %v", value) + } + + value, err = api.SetMonotonicCounter(ctx, endpoint, counterName, 3) + if err != nil { + return fmt.Errorf("second call to SetMonotonicCounter: %w", err) + } + if value != 2 { + return fmt.Errorf("expected previous value 2, got %v", value) + } + + return nil +} diff --git a/coordinator/core/marbleapi.go b/coordinator/core/marbleapi.go index 2fa4d2a1..91ef45f7 100644 --- a/coordinator/core/marbleapi.go +++ b/coordinator/core/marbleapi.go @@ -41,8 +41,9 @@ import ( ) type reservedSecrets struct { - RootCA manifest.Secret - MarbleCert manifest.Secret + RootCA manifest.Secret + MarbleCert manifest.Secret + CoordinatorRoot manifest.Secret } // Defines the "MarbleRun" prefix when mentioned in a manifest. @@ -363,10 +364,15 @@ func customizeParameters(params manifest.Parameters, specialSecrets reservedSecr if err != nil { return nil, fmt.Errorf("encoding marble private key: %w", err) } + coordinatorRootPem, err := manifest.EncodeSecretDataToPem(specialSecrets.CoordinatorRoot.Cert) + if err != nil { + return nil, fmt.Errorf("encoding Coordinator root CA: %w", err) + } customParams.Env[marble.MarbleEnvironmentRootCA] = []byte(rootCaPem) customParams.Env[marble.MarbleEnvironmentCertificateChain] = []byte(marbleCertPem + rootCaPem) customParams.Env[marble.MarbleEnvironmentPrivateKey] = []byte(encodedPrivKey) + customParams.Env[globalconstants.MarbleEnvironmentCoordinatorRootCA] = []byte(coordinatorRootPem) return &customParams, nil } @@ -416,10 +422,16 @@ func (c *Core) generateMarbleAuthSecrets(txdata storeGetter, req *rpc.Activation if err != nil { return reservedSecrets{}, err } + coordinatorRootCert, err := txdata.GetCertificate(constants.SKCoordinatorRootCert) + if err != nil { + return reservedSecrets{}, err + } + // customize marble's parameters authSecrets := reservedSecrets{ - RootCA: manifest.Secret{Cert: manifest.Certificate(*marbleRootCert)}, - MarbleCert: manifest.Secret{Cert: manifest.Certificate(*marbleCert), Public: encodedPubKey, Private: encodedPrivKey}, + RootCA: manifest.Secret{Cert: manifest.Certificate(*marbleRootCert)}, + MarbleCert: manifest.Secret{Cert: manifest.Certificate(*marbleCert), Public: encodedPubKey, Private: encodedPrivKey}, + CoordinatorRoot: manifest.Secret{Cert: manifest.Certificate(*coordinatorRootCert)}, } return authSecrets, nil diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 9e42a351..a3429d94 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -9,4 +9,13 @@ package constants const ( // EnvMarbleTTLSConfig is the name of the environment variable used to pass the TTLS configuration to the Marble. EnvMarbleTTLSConfig = "MARBLE_TTLS_CONFIG" + + // MarbleEnvironmentCertificateChain contains the name of the environment variable holding a marble-specifc PEM encoded certificate. + MarbleEnvironmentCertificateChain = "MARBLE_PREDEFINED_MARBLE_CERTIFICATE_CHAIN" + + // MarbleEnvironmentCoordinatorRootCA contains the name of the environment variable holding a PEM encoded root certificate. + MarbleEnvironmentCoordinatorRootCA = "MARBLE_PREDEFINED_COORDINATOR_ROOT_CA" + + // MarbleEnvironmentPrivateKey contains the name of the environment variable holding a PEM encoded private key belonging to the marble-specific certificate. + MarbleEnvironmentPrivateKey = "MARBLE_PREDEFINED_PRIVATE_KEY" ) diff --git a/test/framework/framework.go b/test/framework/framework.go index a6e8d298..a9b0488e 100644 --- a/test/framework/framework.go +++ b/test/framework/framework.go @@ -278,6 +278,7 @@ func (i IntegrationTest) GetMarbleCmd(ctx context.Context, cfg MarbleConfig) *ex MakeEnv(mconfig.DNSNames, cfg.dnsNames), MakeEnv(mconfig.UUIDFile, uuidFile), MakeEnv("EDG_TEST_ADDR", i.MarbleTestAddr), + MakeEnv(constants.ClientAddr, i.ClientServerAddr), i.SimulationFlag, } return cmd diff --git a/test/integration_test.go b/test/integration_test.go index 39a40bdf..73b3f3a3 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -457,6 +457,24 @@ func TestSignQuote(t *testing.T) { } } +func TestMonotonicCounter(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + f := newFramework(t) + + cfg := framework.NewCoordinatorConfig() + defer cfg.Cleanup() + f.StartCoordinator(f.Ctx, cfg) + + f.TestManifest.Config.FeatureGates = []string{"MonotonicCounter"} + _, err := f.SetManifest(f.TestManifest) + require.NoError(err) + + marbleCfg := framework.NewMarbleConfig(meshServerAddr, "testMarbleMonotonicCounter", "localhost") + defer marbleCfg.Cleanup() + assert.True(f.StartMarbleClient(f.Ctx, marbleCfg)) +} + func newFramework(t *testing.T) *framework.IntegrationTest { f := framework.New(t, *buildDir, simFlag, *noenclave, marbleTestAddr, meshServerAddr, clientServerAddr, IntegrationManifestJSON, UpdateManifest) f.UpdateManifest() diff --git a/test/manifests.go b/test/manifests.go index 4c85299d..fe3c12a3 100644 --- a/test/manifests.go +++ b/test/manifests.go @@ -373,6 +373,15 @@ var IntegrationManifestJSON = `{ } } }, + "testMarbleMonotonicCounter": { + "Package": "backend", + "Parameters": { + "Argv": [ + "./marble", + "monotonic-counter" + ] + } + }, "badMarble": { "Package": "frontend", "Parameters": { From c194077a48b7356df692bf8ac69c300996b3f298 Mon Sep 17 00:00:00 2001 From: Thomas Tendyck Date: Mon, 7 Oct 2024 13:55:15 +0000 Subject: [PATCH 4/4] samples: add EStore with monotonic counter --- samples/estore/README.md | 52 ++++++++++++++ samples/estore/enclave.json | 15 ++++ samples/estore/go.mod | 45 ++++++++++++ samples/estore/go.sum | 130 +++++++++++++++++++++++++++++++++++ samples/estore/main.go | 64 +++++++++++++++++ samples/estore/manifest.json | 29 ++++++++ samples/helloc++/README.md | 2 +- samples/helloworld/README.md | 4 +- 8 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 samples/estore/README.md create mode 100644 samples/estore/enclave.json create mode 100644 samples/estore/go.mod create mode 100644 samples/estore/go.sum create mode 100644 samples/estore/main.go create mode 100644 samples/estore/manifest.json diff --git a/samples/estore/README.md b/samples/estore/README.md new file mode 100644 index 00000000..e374b206 --- /dev/null +++ b/samples/estore/README.md @@ -0,0 +1,52 @@ +# Using EStore with a monotonic counter for rollback protection + +This example shows how you can use [EStore](https://github.com/edgelesssys/estore) in an EGo Marble to store sensitive information in a structured way. +The encryption key is managed by the MarbleRun Coordinator. + +Optionally, EStore can use a monotonic counter provided the the Coordinator for rollback protection. + +Before proceeding, you should have successfully run the [helloworld sample](../helloworld). + +**Note:** You can run this example on any hardware by simulating the enclave through setting `OE_SIMULATION=1` as environment variable. This might help you to get started with the development of confidential apps. However, please notice that this bypasses any security. Detailed information on how to develop secure Marbles can be found in [MarbleRun's documentation](https://docs.edgeless.systems/marblerun/workflows/add-service). + +You can build and sign the example (or your app) like this: + +```sh +ego-go build -tags marblerun_ego_enclave +ego sign estore-sample +``` + +Get the enclave's unique ID aka `MRENCLAVE` with + +```sh +ego uniqueid estore-sample +``` + +and set it as `UniqueID` in `manifest.json`. + +Next, use the `erthost` command to start the Coordinator in a local enclave: + +```sh +erthost ../../build/coordinator-enclave.signed +``` + +The Coordinator exposes two APIs, a client API (port 4433) and a mesh API (port 2001). While your Marble activates itself via the mesh API, you can administrate the Coordinator via the client API. + +You can now upload the manifest to the Coordinator's client API: + +```sh +curl -k --data-binary @manifest.json https://localhost:4433/manifest +``` + +Finaly, you can run the helloworld Marble (or whatever Marble you just created) with the `ego marblerun` command. You need to set `EDG_MARBLE_TYPE` to a Marble that was defined in the `manifest.json`. In this example, the manifest defines a single Marble, which is called "estore-marble". +You also need to set the Coordinator client API address so the Marble can call the monotonic counter API. + +```sh +EDG_COORDINATOR_CLIENT_ADDR=localhost:4433 EDG_MARBLE_TYPE=estore-marble ego marblerun estore-sample +``` + +EGo starts the Marble, which will then connect itself to the mesh API of the Coordinator. +After activation, it will add a new value to the store and print all values added so far. +You can run the Marble multiple times. +You can see the rollback protection in action if you copy the `db` folder at some point in time, run the Marble again, and then copy the folder back. +The Marble will refuse to open the old DB state. diff --git a/samples/estore/enclave.json b/samples/estore/enclave.json new file mode 100644 index 00000000..08e98715 --- /dev/null +++ b/samples/estore/enclave.json @@ -0,0 +1,15 @@ +{ + "exe": "estore-sample", + "key": "private.pem", + "debug": true, + "heapSize": 512, + "productID": 1, + "securityVersion": 1, + "mounts": [ + { + "source": "db", + "target": "/db", + "type": "hostfs" + } + ] +} diff --git a/samples/estore/go.mod b/samples/estore/go.mod new file mode 100644 index 00000000..bf667da2 --- /dev/null +++ b/samples/estore/go.mod @@ -0,0 +1,45 @@ +module estore-sample + +go 1.22.0 + +require ( + github.com/edgelesssys/estore v1.1.1-0.20241001114403-e9f59787f226 + github.com/edgelesssys/marblerun v1.5.3-0.20241007134742-69fe31b6e209 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/edgelesssys/ego v1.5.5-0.20240925073404-7bac2575a025 // indirect + github.com/getsentry/sentry-go v0.29.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.20.4 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect +) diff --git a/samples/estore/go.sum b/samples/estore/go.sum new file mode 100644 index 00000000..5d685705 --- /dev/null +++ b/samples/estore/go.sum @@ -0,0 +1,130 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/edgelesssys/ego v1.5.5-0.20240925073404-7bac2575a025 h1:DFxfU3oaqOl7tkLJ5qvS+eolJntuYABwOMx6DNO7GUQ= +github.com/edgelesssys/ego v1.5.5-0.20240925073404-7bac2575a025/go.mod h1:t10m29KSwG2hKwWFIq7/vuzfoKhPIdevOXx8nm636iU= +github.com/edgelesssys/estore v1.1.1-0.20241001114403-e9f59787f226 h1:oTBXNz2miFHwJaN3CMo8R7rQagBd7d2n4VxZ+N2RnGg= +github.com/edgelesssys/estore v1.1.1-0.20241001114403-e9f59787f226/go.mod h1:6VwPBea8aA5NsHdNfe+6U7vqDgFHANVhsfk4WZq5kWI= +github.com/edgelesssys/marblerun v1.5.3-0.20241007134742-69fe31b6e209 h1:GmAuUrLaVc0RKYKl6QKlQa3UnN7LBAeMtMfTAEq2V4k= +github.com/edgelesssys/marblerun v1.5.3-0.20241007134742-69fe31b6e209/go.mod h1:br2K39rkfS3Trz4JBr6pYPjEGOLnhuQU9k19E5QKvzo= +github.com/getsentry/sentry-go v0.29.0 h1:YtWluuCFg9OfcqnaujpY918N/AhCCwarIDWOYSBAjCA= +github.com/getsentry/sentry-go v0.29.0/go.mod h1:jhPesDAL0Q0W2+2YEuVOvdWmVtdsr1+jtBrlDEVWwLY= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/samples/estore/main.go b/samples/estore/main.go new file mode 100644 index 00000000..87e89045 --- /dev/null +++ b/samples/estore/main.go @@ -0,0 +1,64 @@ +// Copyright (c) Edgeless Systems GmbH. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package main + +import ( + "context" + "encoding/base64" + "fmt" + "log" + "math/rand" + "os" + + "github.com/edgelesssys/estore" + "github.com/edgelesssys/marblerun/api" +) + +func main() { + // Get encryption key from secure environment + encryptionKey, err := base64.StdEncoding.DecodeString(os.Getenv("ENCRYPTION_KEY")) + if err != nil { + log.Fatal(err) + } + + // Create an encrypted store and enable rollback protection by using a monotonic counter provided by the Coordinator + opts := &estore.Options{ + EncryptionKey: encryptionKey, + SetMonotonicCounter: setMonotonicCounter, + } + db, err := estore.Open("db", opts) + if err != nil { + log.Fatal(err) + } + defer db.Close() + + tx := db.NewTransaction(true) + defer tx.Close() + + // Set a key-value pair + key := fmt.Appendf(nil, "hello %v", rand.Int()) + value := fmt.Appendf(nil, "world %v", rand.Int()) + if err := tx.Set(key, value, nil); err != nil { + log.Fatal(err) + } + + // Print all key-value pairs added so far + iter := tx.NewIter(&estore.IterOptions{LowerBound: []byte("hello"), UpperBound: []byte("hello~")}) + for iter.First(); iter.Valid(); iter.Next() { + fmt.Printf("%s = %s\n", iter.Key(), iter.Value()) + } + iter.Close() + + if err := tx.Commit(); err != nil { + log.Fatal(err) + } +} + +func setMonotonicCounter(value uint64) (uint64, error) { + endpoint := os.Getenv("EDG_COORDINATOR_CLIENT_ADDR") + return api.SetMonotonicCounter(context.Background(), endpoint, "my-estore-counter", value) +} diff --git a/samples/estore/manifest.json b/samples/estore/manifest.json new file mode 100644 index 00000000..3108178f --- /dev/null +++ b/samples/estore/manifest.json @@ -0,0 +1,29 @@ +{ + "Packages": { + "estore-package": { + "Debug": true, + "UniqueID": "" + } + }, + "Marbles": { + "estore-marble": { + "Package": "estore-package", + "Parameters": { + "Env": { + "ENCRYPTION_KEY": "{{ base64 .Secrets.encryptionKey }}" + } + } + } + }, + "Secrets": { + "encryptionKey": { + "Type": "symmetric-key", + "Size": 128 + } + }, + "Config": { + "FeatureGates": [ + "MonotonicCounter" + ] + } +} diff --git a/samples/helloc++/README.md b/samples/helloc++/README.md index 2465a0ef..883a7d3d 100644 --- a/samples/helloc++/README.md +++ b/samples/helloc++/README.md @@ -2,7 +2,7 @@ This example shows how to build a confidential C++ application and run it in MarbleRun. This can serve you as a blueprint for making existing applications MarbleRun-ready or creating new [Marbles](https://docs.edgeless.systems/marblerun/architecture/marbles). If you haven't already, [setup MarbleRun](../../BUILD.md#build) to get ready. -**Note:** You can run this example on any hardware by simulating the enclave through setting `OE_SIMULATION=1` as environment variable. This might help you to get started with with the development of confidential apps. However, please notice that this bypasses any security. Detailed information on how to develop secure Marbles can be found in [MarbleRun's documentation](https://docs.edgeless.systems/marblerun/workflows/add-service). +**Note:** You can run this example on any hardware by simulating the enclave through setting `OE_SIMULATION=1` as environment variable. This might help you to get started with the development of confidential apps. However, please notice that this bypasses any security. Detailed information on how to develop secure Marbles can be found in [MarbleRun's documentation](https://docs.edgeless.systems/marblerun/workflows/add-service). The directory `app` contains the application code: diff --git a/samples/helloworld/README.md b/samples/helloworld/README.md index 02fe2749..80db9c1e 100644 --- a/samples/helloworld/README.md +++ b/samples/helloworld/README.md @@ -2,7 +2,7 @@ This example shows how to build a confidential Go application with [EGo](https://ego.dev) and run it in MarbleRun. This can serve you as a blueprint for making existing applications MarbleRun-ready or creating new [Marbles](https://docs.edgeless.systems/marblerun/architecture/marbles). If you haven't already, [setup MarbleRun](../../BUILD.md#build) and EGo to get ready. -**Note:** You can run this example on any hardware by simulating the enclave through setting `OE_SIMULATION=1` as environment variable. This might help you to get started with with the development of confidential apps. However, please notice that this bypasses any security. Detailed information on how to develop secure Marbles can be found in [MarbleRun's documentation](https://docs.edgeless.systems/marblerun/workflows/add-service). +**Note:** You can run this example on any hardware by simulating the enclave through setting `OE_SIMULATION=1` as environment variable. This might help you to get started with the development of confidential apps. However, please notice that this bypasses any security. Detailed information on how to develop secure Marbles can be found in [MarbleRun's documentation](https://docs.edgeless.systems/marblerun/workflows/add-service). You can build and sign the example (or your app) like this: @@ -19,7 +19,7 @@ ego uniqueid hello and set it as `UniqueID` in `manifest.json`. -Next, use the `erthost` command to start the Coordinator in a local simulated enclave: +Next, use the `erthost` command to start the Coordinator in a local enclave: ```sh erthost ../../build/coordinator-enclave.signed