diff --git a/cmd/premain-libos/main.go b/cmd/premain-libos/main.go index fedaf87f..0ce8f5c6 100644 --- a/cmd/premain-libos/main.go +++ b/cmd/premain-libos/main.go @@ -14,7 +14,10 @@ import ( "path/filepath" "strings" + "github.com/edgelesssys/marblerun/internal/constants" + "github.com/edgelesssys/marblerun/marble/premain" marblePremain "github.com/edgelesssys/marblerun/marble/premain" + "github.com/edgelesssys/marblerun/util" "github.com/spf13/afero" "golang.org/x/sys/unix" ) @@ -26,7 +29,9 @@ const ( occlum ) -func exit(format string, args ...interface{}) { +var exit func(format string, args ...interface{}) + +func exitStdLog(format string, args ...interface{}) { // Print error message in red and append newline // then exit with error code 1 msg := fmt.Sprintf("Error: %s\n", format) @@ -35,6 +40,19 @@ func exit(format string, args ...interface{}) { } func main() { + exit = exitStdLog + if strings.EqualFold(util.Getenv(constants.EnvLogFormat, ""), constants.LogFormatJSON) { + zapLog, err := premain.LogJSON() + if err != nil { + exit("failed to initialize logger: %s", err) + } + defer zapLog.Sync() + + exit = func(format string, args ...interface{}) { + zapLog.Fatal(fmt.Sprintf(format, args...)) + } + } + log.SetPrefix("[PreMain] ") // Automatically detect libOS based on uname diff --git a/docs/docs/workflows/add-service.md b/docs/docs/workflows/add-service.md index 568362d1..c87f2bd3 100644 --- a/docs/docs/workflows/add-service.md +++ b/docs/docs/workflows/add-service.md @@ -5,6 +5,7 @@ Adding a service to your application requires three steps, which are described i ## **Step 1:** Get your service ready for MarbleRun To get your service ready for MarbleRun, you need to rebuild it with one of the supported [runtimes](../features/runtimes.md): + * [EGo](../building-marbles/ego.md) * [Edgeless RT](https://github.com/edgelesssys/marblerun/blob/master/samples/helloc%2B%2B) * [Gramine](../building-marbles/gramine.md) @@ -29,6 +30,7 @@ Now that your service is ready, you need to make two types of entries in the man As is described in more detail in the [writing a manifest hands-on](../workflows/define-manifest.md#packages), the manifest contains a section `Packages`, in which allowed enclave software packages are defined. #### EGo / EdgelessRT + To add an entry for your EGo / EdgelessRT service, run the `oesign` tool on the enclave file you built in the previous step as follows. (`oesign` is installed with [Edgeless RT](https://github.com/edgelesssys/edgelessrt).) ```bash @@ -50,7 +52,6 @@ The tool's output is similar to the following. To add an entry for your Gramine service, run the `gramine-sgx-get-token` tool on the `.sig` file you built in the previous step as follows. (`gramine-sgx-get-token` is installed with [Gramine](https://github.com/gramineproject/gramine/).) - ```bash gramine-sgx-get-token --sig hello.sig ``` @@ -91,7 +92,6 @@ ProductID (ISVPRODID) : 1 SecurityVersion (ISVSVN) : 3 ``` - Use `UniqueID` (i.e., `MRENCLAVE` in Intel SGX speak) or the triplet of `SignerID` (i.e., `MRSIGNER`), `SecurityVersion`, and `ProductID` to add an entry in the `Packages` section. ### **Step 2.2:** Define the parameters @@ -125,6 +125,8 @@ The environment variables have the following purposes. * `EDG_MARBLE_DNS_NAMES` is the list of DNS names and IPs the Coordinator will issue the Marble's certificate for. +Optionally, you can set the `EDG_LOG_FORMAT` environment variable to `json` to enable JSON structured logs for the Marble's startup code. + ## **Step 4:** Deploy your service with Kubernetes Typically, you'll write a Kubernetes resource definition for your service, which you'll deploy with the Kubernetes CLI, Helm, or similar tools. diff --git a/injector/injector.go b/injector/injector.go index a364c41b..c994200a 100644 --- a/injector/injector.go +++ b/injector/injector.go @@ -112,11 +112,15 @@ func (m *Mutator) mutate(body []byte) ([]byte, error) { marbleType, exists := pod.Labels[labelMarbleType] if !exists { // admission request was sent for a pod without marblerun/marbletype label, this should not happen - return m.generateResponse(pod, admReviewReq, admReviewResponse, false, fmt.Sprintf("Missing [%s] label, request denied", labelMarbleType)) + msg := fmt.Sprintf("Missing [%s] label, request denied", labelMarbleType) + m.log.Error(msg) + return m.generateResponse(pod, admReviewReq, admReviewResponse, false, msg) } if len(marbleType) <= 0 { // deny request if the label exists, but is empty - return m.generateResponse(pod, admReviewReq, admReviewResponse, false, fmt.Sprintf("Empty [%s] label, request denied", labelMarbleType)) + msg := fmt.Sprintf("Empty [%s] label, request denied", labelMarbleType) + m.log.Error(msg) + return m.generateResponse(pod, admReviewReq, admReviewResponse, false, msg) } injectSgx := false @@ -244,7 +248,15 @@ func (m *Mutator) mutate(body []byte) ([]byte, error) { }) } - return m.generateResponse(pod, admReviewReq, admReviewResponse, true, fmt.Sprintf("Mutation request for pod of marble type [%s] successful", marbleType)) + msg := fmt.Sprintf("Mutation request for pod of marble type [%s] successful", marbleType) + resp, err := m.generateResponse(pod, admReviewReq, admReviewResponse, true, msg) + if err != nil { + m.log.Error("Unable to mutate request: response generation failed", zap.Error(err)) + return nil, err + } + + m.log.Info(msg) + return resp, nil } // checkRequest verifies the request used was POST and not empty. @@ -321,7 +333,5 @@ func (m *Mutator) generateResponse(pod corev1.Pod, request, response v1.Admissio return nil, fmt.Errorf("unable to marshal admission response: %w", err) } - m.log.Info(message) - return bytes, nil } diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 7614acae..5e2c4f05 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -7,6 +7,12 @@ SPDX-License-Identifier: BUSL-1.1 package constants const ( + // EnvLogFormat is the name of the environment variable used to pass the log format to the PreMain. + // Should be "json" for JSON formatted logs, or any other value for human-readable logs. + EnvLogFormat = "EDG_LOG_FORMAT" + // LogFormatJSON indicates that logs should be formatted as JSON. + LogFormatJSON = "json" + // EnvMarbleTTLSConfig is the name of the environment variable used to pass the TTLS configuration to the Marble. EnvMarbleTTLSConfig = "MARBLE_TTLS_CONFIG" diff --git a/marble/premain/premain.go b/marble/premain/premain.go index e82873a7..3c25b77c 100644 --- a/marble/premain/premain.go +++ b/marble/premain/premain.go @@ -24,14 +24,26 @@ import ( "github.com/edgelesssys/marblerun/coordinator/quote/ertvalidator" "github.com/edgelesssys/marblerun/coordinator/rpc" "github.com/edgelesssys/marblerun/internal/constants" + "github.com/edgelesssys/marblerun/internal/logging" "github.com/edgelesssys/marblerun/marble/config" "github.com/edgelesssys/marblerun/util" "github.com/google/uuid" "github.com/spf13/afero" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) +// LogJSON replaces Go's standard logger with [*zap.Logger] to log in JSON format. +func LogJSON() (*zap.Logger, error) { + zapLogger, err := logging.New() + if err != nil { + return nil, err + } + zap.RedirectStdLog(zapLogger) + return zapLogger, nil +} + // storeUUID stores the uuid to the fs. func storeUUID(appFs afero.Fs, marbleUUID uuid.UUID, filename string) error { uuidBytes, err := marbleUUID.MarshalText() @@ -126,6 +138,14 @@ func PreMainMock() error { // //nolint:revive func PreMainEx(issuer quote.Issuer, activate ActivateFunc, hostfs, enclavefs afero.Fs) error { + if strings.EqualFold(util.Getenv(constants.EnvLogFormat, ""), constants.LogFormatJSON) { + zapLog, err := LogJSON() + if err != nil { + return err + } + defer zapLog.Sync() + } + prefixBackup := log.Prefix() defer log.SetPrefix(prefixBackup) log.SetPrefix("[PreMain] ")