From e77791dd10c885825151e68e3a96af9d29d1793c Mon Sep 17 00:00:00 2001 From: "Arvid E. Picciani" Date: Wed, 13 Jul 2022 15:09:50 +0200 Subject: [PATCH] implement a log of events queriable from the metrics endpoint fixes #304 Signed-off-by: Arvid E. Picciani --- cmd/coordinator/run.go | 6 ++-- coordinator/core/core.go | 7 ++-- coordinator/core/core_test.go | 12 +++---- coordinator/core/marbleapi.go | 6 ++++ coordinator/core/marbleapi_test.go | 8 ++--- coordinator/core/metrics_test.go | 6 ++-- coordinator/core/openssl_test.go | 2 +- coordinator/events/eventlog.go | 54 ++++++++++++++++++++++++++++++ coordinator/server/server.go | 4 ++- 9 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 coordinator/events/eventlog.go diff --git a/cmd/coordinator/run.go b/cmd/coordinator/run.go index 92a89d0b..166a9779 100644 --- a/cmd/coordinator/run.go +++ b/cmd/coordinator/run.go @@ -13,6 +13,7 @@ import ( "github.com/edgelesssys/marblerun/coordinator/config" "github.com/edgelesssys/marblerun/coordinator/core" + "github.com/edgelesssys/marblerun/coordinator/events" "github.com/edgelesssys/marblerun/coordinator/quote" "github.com/edgelesssys/marblerun/coordinator/recovery" "github.com/edgelesssys/marblerun/coordinator/seal" @@ -57,6 +58,7 @@ func run(validator quote.Validator, issuer quote.Issuer, sealDir string, sealer promServerAddr := os.Getenv(config.PromAddr) // Create Prometheus resources and start the Prometheus server. + var eventlog = events.NewLog() var promRegistry *prometheus.Registry var promFactoryPtr *promauto.Factory if promServerAddr != "" { @@ -72,7 +74,7 @@ func run(validator quote.Validator, issuer quote.Issuer, sealDir string, sealer "commit": GitCommit, }, }) - go server.RunPrometheusServer(promServerAddr, zapLogger, promRegistry) + go server.RunPrometheusServer(promServerAddr, zapLogger, promRegistry, eventlog) } // creating core @@ -80,7 +82,7 @@ func run(validator quote.Validator, issuer quote.Issuer, sealDir string, sealer if err := os.MkdirAll(sealDir, 0o700); err != nil { zapLogger.Fatal("Cannot create or access sealdir. Please check the permissions for the specified path.", zap.Error(err)) } - co, err := core.NewCore(dnsNames, validator, issuer, sealer, recovery, zapLogger, promFactoryPtr) + co, err := core.NewCore(dnsNames, validator, issuer, sealer, recovery, zapLogger, promFactoryPtr, eventlog) if err != nil { if _, ok := err.(core.QuoteError); !ok || !devMode { zapLogger.Fatal("Cannot create Coordinator core", zap.Error(err)) diff --git a/coordinator/core/core.go b/coordinator/core/core.go index 196b9f0b..fd7f44d4 100644 --- a/coordinator/core/core.go +++ b/coordinator/core/core.go @@ -26,6 +26,7 @@ import ( "sync" "time" + "github.com/edgelesssys/marblerun/coordinator/events" "github.com/edgelesssys/marblerun/coordinator/manifest" "github.com/edgelesssys/marblerun/coordinator/quote" "github.com/edgelesssys/marblerun/coordinator/recovery" @@ -55,6 +56,7 @@ type Core struct { updateLogger *updatelog.Logger zaplogger *zap.Logger metrics *coreMetrics + eventlog *events.Log rpc.UnimplementedMarbleServer } @@ -112,7 +114,7 @@ func (c *Core) advanceState(newState state, tx store.Transaction) error { } // NewCore creates and initializes a new Core object. -func NewCore(dnsNames []string, qv quote.Validator, qi quote.Issuer, sealer seal.Sealer, recovery recovery.Recovery, zapLogger *zap.Logger, promFactory *promauto.Factory) (*Core, error) { +func NewCore(dnsNames []string, qv quote.Validator, qi quote.Issuer, sealer seal.Sealer, recovery recovery.Recovery, zapLogger *zap.Logger, promFactory *promauto.Factory, eventlog *events.Log) (*Core, error) { stor := store.NewStdStore(sealer) c := &Core{ qv: qv, @@ -122,6 +124,7 @@ func NewCore(dnsNames []string, qv quote.Validator, qi quote.Issuer, sealer seal data: storeWrapper{store: stor}, sealer: sealer, zaplogger: zapLogger, + eventlog: eventlog, } c.metrics = newCoreMetrics(promFactory, c, "coordinator") @@ -207,7 +210,7 @@ func NewCoreWithMocks() *Core { issuer := quote.NewMockIssuer() sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - core, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + core, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) if err != nil { panic(err) } diff --git a/coordinator/core/core_test.go b/coordinator/core/core_test.go index 9a5392a7..8dccada7 100644 --- a/coordinator/core/core_test.go +++ b/coordinator/core/core_test.go @@ -73,7 +73,7 @@ func TestSeal(t *testing.T) { sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) // Set manifest. This will seal the state. @@ -90,7 +90,7 @@ func TestSeal(t *testing.T) { assert.NoError(err) // Check sealing with a new core initialized with the sealed state. - c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) c2State, err := c2.data.getState() assert.NoError(err) @@ -127,7 +127,7 @@ func TestRecover(t *testing.T) { sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) // new core does not allow recover @@ -145,7 +145,7 @@ func TestRecover(t *testing.T) { // Initialize new core and let unseal fail sealer.UnsealError = seal.ErrEncryptionKey - c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) sealer.UnsealError = nil require.NoError(err) c2State, err := c2.data.getState() @@ -367,7 +367,7 @@ func TestUnsetRestart(t *testing.T) { recovery := recovery.NewSinglePartyRecovery() // create a new core, this seals the state with only certificate and keys - c1, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c1, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) c1State, err := c1.data.getState() assert.NoError(err) @@ -376,7 +376,7 @@ func TestUnsetRestart(t *testing.T) { assert.NoError(err) // create a second core, this should overwrite the previously sealed certificate and keys since no manifest was set - c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + c2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) c2State, err := c2.data.getState() assert.NoError(err) diff --git a/coordinator/core/marbleapi.go b/coordinator/core/marbleapi.go index dead0e74..cf950b99 100644 --- a/coordinator/core/marbleapi.go +++ b/coordinator/core/marbleapi.go @@ -145,6 +145,12 @@ func (c *Core) Activate(ctx context.Context, req *rpc.ActivationReq) (*rpc.Activ c.metrics.marbleAPI.activationSuccess.WithLabelValues(req.GetMarbleType(), req.GetUUID()).Inc() c.zaplogger.Info("Successfully activated new Marble", zap.String("MarbleType", req.MarbleType), zap.String("UUID", marbleUUID.String())) + + if c.eventlog != nil { + c.eventlog.Activation(req.GetMarbleType(), req.GetUUID(), req.GetQuote()) + } + + return resp, nil } diff --git a/coordinator/core/marbleapi_test.go b/coordinator/core/marbleapi_test.go index a1e5e4da..46c6907b 100644 --- a/coordinator/core/marbleapi_test.go +++ b/coordinator/core/marbleapi_test.go @@ -53,7 +53,7 @@ func TestActivate(t *testing.T) { issuer := quote.NewMockIssuer() sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) require.NotNil(coreServer) @@ -461,7 +461,7 @@ func TestSecurityLevelUpdate(t *testing.T) { issuer := quote.NewMockIssuer() sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) require.NotNil(coreServer) @@ -491,7 +491,7 @@ func TestSecurityLevelUpdate(t *testing.T) { spawner.newMarble("frontend", "Azure", false) // Use a new core and test if updated manifest persisted after restart - coreServer2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + coreServer2, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) coreServer2State, err := coreServer2.data.getState() assert.NoError(err) @@ -572,7 +572,7 @@ func TestActivateWithMissingParameters(t *testing.T) { issuer := quote.NewMockIssuer() sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) require.NotNil(coreServer) diff --git a/coordinator/core/metrics_test.go b/coordinator/core/metrics_test.go index b80c92dc..a6d92126 100644 --- a/coordinator/core/metrics_test.go +++ b/coordinator/core/metrics_test.go @@ -35,7 +35,7 @@ func TestStoreWrapperMetrics(t *testing.T) { // reg := prometheus.NewRegistry() fac := promauto.With(reg) - c, _ := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &fac) + c, _ := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &fac, nil) assert.Equal(1, promtest.CollectAndCount(c.metrics.coordinatorState)) assert.Equal(float64(stateAcceptingManifest), promtest.ToFloat64(c.metrics.coordinatorState)) @@ -49,7 +49,7 @@ func TestStoreWrapperMetrics(t *testing.T) { reg = prometheus.NewRegistry() fac = promauto.With(reg) sealer.UnsealError = seal.ErrEncryptionKey - c, err = NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &fac) + c, err = NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &fac, nil) sealer.UnsealError = nil require.NoError(err) assert.Equal(1, promtest.CollectAndCount(c.metrics.coordinatorState)) @@ -84,7 +84,7 @@ func TestMarbleAPIMetrics(t *testing.T) { recovery := recovery.NewSinglePartyRecovery() promRegistry := prometheus.NewRegistry() promFactory := promauto.With(promRegistry) - c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &promFactory) + c, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, &promFactory, nil) require.NoError(err) require.NotNil(c) diff --git a/coordinator/core/openssl_test.go b/coordinator/core/openssl_test.go index 4db709b2..b086e0ae 100644 --- a/coordinator/core/openssl_test.go +++ b/coordinator/core/openssl_test.go @@ -52,7 +52,7 @@ func TestOpenSSLVerify(t *testing.T) { issuer := quote.NewMockIssuer() sealer := &seal.MockSealer{} recovery := recovery.NewSinglePartyRecovery() - coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil) + coreServer, err := NewCore([]string{"localhost"}, validator, issuer, sealer, recovery, zapLogger, nil, nil) require.NoError(err) require.NotNil(coreServer) diff --git a/coordinator/events/eventlog.go b/coordinator/events/eventlog.go new file mode 100644 index 00000000..98a73b1f --- /dev/null +++ b/coordinator/events/eventlog.go @@ -0,0 +1,54 @@ +// 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 events implements a log of coordinator events. +package events + +import ( + "encoding/json" + "net/http" + "time" +) + +// ActivationEvent is an event that is logged when a marble is activated. +type ActivationEvent struct { + MarbleType string `json:"marbleType"` + UUID string `json:"uuid"` + Quote []byte `json:"quote"` +} + +// Event represents a single event in the event log. +type Event struct { + Timestamp time.Time `json:"time"` + Activation *ActivationEvent `json:"activation"` +} + +// Log is a log of coordinator events. +type Log struct { + events []Event +} + +// NewLog creates a new log. +func NewLog() *Log { + return &Log{} +} + +// Activation adds an activation event to the log. +func (l *Log) Activation(marbleType string, uuid string, quote []byte) { + l.events = append(l.events, Event{ + Timestamp: time.Now(), + Activation: &ActivationEvent{MarbleType: marbleType, UUID: uuid, Quote: quote}, + }) +} + +// The http handler retutns the log as JSON array +func (l *Log) Handler() http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(l.events) + }) +} diff --git a/coordinator/server/server.go b/coordinator/server/server.go index c3c5218e..af2db6b4 100644 --- a/coordinator/server/server.go +++ b/coordinator/server/server.go @@ -14,6 +14,7 @@ import ( "os" "github.com/edgelesssys/marblerun/coordinator/core" + "github.com/edgelesssys/marblerun/coordinator/events" "github.com/edgelesssys/marblerun/coordinator/rpc" "github.com/gorilla/handlers" "github.com/gorilla/mux" @@ -112,9 +113,10 @@ func RunClientServer(mux http.Handler, address string, tlsConfig *tls.Config, za } // RunPrometheusServer runs a HTTP server handling the prometheus metrics endpoint. -func RunPrometheusServer(address string, zapLogger *zap.Logger, reg *prometheus.Registry) { +func RunPrometheusServer(address string, zapLogger *zap.Logger, reg *prometheus.Registry, eventlog *events.Log) { mux := http.NewServeMux() mux.Handle("/metrics", promhttp.InstrumentMetricHandler(reg, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))) + mux.Handle("/events", eventlog.Handler()) zapLogger.Info("starting prometheus /metrics endpoint", zap.String("address", address)) err := http.ListenAndServe(address, mux) zapLogger.Warn(err.Error())