From 74c03ca05da7d5e698d9c31936f024f7b05ea364 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Thu, 21 Nov 2024 07:17:25 -0600 Subject: [PATCH 1/9] Add flag to run enclave application. This makes bundling veil with an application easier because we no longer have to add a shell script to the Docker image that runs both veil and the enclave application. --- cmd/veil/main.go | 77 ++++++++++++++++++++++++++++++ cmd/veil/main_test.go | 42 +++++++--------- internal/config/config.go | 10 ++++ internal/httputil/httputil.go | 42 ++++++++++++++-- internal/httputil/httputil_test.go | 45 +++++++++++++++++ 5 files changed, 188 insertions(+), 28 deletions(-) diff --git a/cmd/veil/main.go b/cmd/veil/main.go index 38d8fe2..24f1277 100644 --- a/cmd/veil/main.go +++ b/cmd/veil/main.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "context" "errors" "flag" @@ -9,13 +10,17 @@ import ( "log" "net/url" "os" + "os/exec" "os/signal" + "strings" + "time" "github.com/Amnesic-Systems/veil/internal/config" "github.com/Amnesic-Systems/veil/internal/enclave" "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" "github.com/Amnesic-Systems/veil/internal/errs" + "github.com/Amnesic-Systems/veil/internal/httputil" "github.com/Amnesic-Systems/veil/internal/service" "github.com/Amnesic-Systems/veil/internal/tunnel" "github.com/Amnesic-Systems/veil/internal/util" @@ -30,6 +35,11 @@ func parseFlags(out io.Writer, args []string) (*config.Config, error) { fs := flag.NewFlagSet("veil", flag.ContinueOnError) fs.SetOutput(out) + appCmd := fs.String( + "app-cmd", + "", + "command to run to invoke application", + ) appWebSrv := fs.String( "app-web-srv", "localhost:8081", @@ -83,6 +93,7 @@ func parseFlags(out io.Writer, args []string) (*config.Config, error) { // Build and validate the config. return &config.Config{ + AppCmd: *appCmd, AppWebSrv: util.Must(url.Parse(*appWebSrv)), Debug: *debug, EnclaveCodeURI: *enclaveCodeURI, @@ -120,6 +131,17 @@ func run(ctx context.Context, out io.Writer, args []string) (err error) { return err } + // Run the application command, if specified. + if cfg.AppCmd != "" { + go func() { + if err := eventuallyRunAppCmd(ctx, cfg, cfg.AppCmd); err != nil { + log.Printf("App unavailable: %v", err) + // Shut down the service if the app command fails. + cancel() + } + }() + } + // Initialize dependencies and start the service. var attester enclave.Attester = nitro.NewAttester() var tunneler tunnel.Mechanism = tunnel.NewVSOCK() @@ -131,6 +153,61 @@ func run(ctx context.Context, out io.Writer, args []string) (err error) { return nil } +func eventuallyRunAppCmd(ctx context.Context, cfg *config.Config, cmd string) (err error) { + defer errs.Wrap(&err, "failed to run app command") + + // Wait for the internal service to be ready. + deadlineCtx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancel() + url := fmt.Sprintf("http://localhost:%d", cfg.IntPort) + if err := httputil.WaitForSvc(deadlineCtx, httputil.NewNoAuthHTTPClient(), url); err != nil { + return err + } + log.Print("Internal service ready; running app command.") + + return runAppCmd(ctx, cmd) +} + +func runAppCmd(ctx context.Context, cmdStr string) error { + args := strings.Split(cmdStr, " ") + cmd := exec.CommandContext(ctx, args[0], args[1:]...) + + // Print the enclave application's stdout and stderr. + appStderr, err := cmd.StderrPipe() + if err != nil { + return err + } + appStdout, err := cmd.StdoutPipe() + if err != nil { + return err + } + go forward(appStderr, io.Discard) + go forward(appStdout, io.Discard) + + // Start the application and wait for it to terminate. + log.Println("Starting application.") + if err := cmd.Start(); err != nil { + return err + } + log.Println("Waiting for application to terminate.") + if err := cmd.Wait(); err != nil { + return err + } + log.Println("Application terminated.") + + return nil +} + +func forward(from io.Reader, to io.Writer) { + s := bufio.NewScanner(from) + for s.Scan() { + fmt.Fprintln(to, s.Text()) + } + if err := s.Err(); err != nil { + log.Printf("Error reading application output: %v", err) + } +} + func main() { if err := run(context.Background(), os.Stdout, os.Args[1:]); err != nil { log.Fatalf("Failed to run veil: %v", err) diff --git a/cmd/veil/main_test.go b/cmd/veil/main_test.go index ebd7851..ca550b2 100644 --- a/cmd/veil/main_test.go +++ b/cmd/veil/main_test.go @@ -5,7 +5,6 @@ import ( "context" "crypto/sha256" "encoding/json" - "errors" "flag" "fmt" "io" @@ -24,6 +23,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" "github.com/Amnesic-Systems/veil/internal/httperr" + "github.com/Amnesic-Systems/veil/internal/httputil" "github.com/Amnesic-Systems/veil/internal/nonce" "github.com/Amnesic-Systems/veil/internal/service/attestation" "github.com/Amnesic-Systems/veil/internal/testutil" @@ -40,26 +40,6 @@ func withFlags(flag ...string) []string { return append(f, flag...) } -func waitForSvc(t *testing.T, url string) error { - var ( - start = time.Now() - retry = time.NewTicker(5 * time.Millisecond) - deadline = time.Second - ) - - for range retry.C { - if _, err := testutil.Client.Get(url); err == nil { - return nil - } - if time.Since(start) > deadline { - t.Logf("Web server %s still unavailable after %v.", url, deadline) - return errors.New("timeout") - } - } - - return nil -} - func startSvc(t *testing.T, cfg []string) func() { var ( ctx, cancelCtx = context.WithCancel(context.Background()) @@ -78,13 +58,25 @@ func startSvc(t *testing.T, cfg []string) func() { }(ctx, wg) // Block until the services are ready. - if err := waitForSvc(t, intSrv("/")); err != nil { - t.Logf("error waiting for service: %v", err) + deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancelFunc() + if err := httputil.WaitForSvc( + deadline, + httputil.NewNoAuthHTTPClient(), + intSrv("/"), + ); err != nil { + t.Logf("error waiting for internal service: %v", err) return f } if !slices.Contains(cfg, "-wait-for-app") { - if err := waitForSvc(t, extSrv("/")); err != nil { - t.Logf("error waiting for service: %v", err) + deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancelFunc() + if err := httputil.WaitForSvc( + deadline, + httputil.NewNoAuthHTTPClient(), + extSrv("/"), + ); err != nil { + t.Logf("error waiting for external service: %v", err) return f } } diff --git a/internal/config/config.go b/internal/config/config.go index 6bae256..68ad13f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,6 +11,16 @@ var _ = util.Validator(&Config{}) // Config represents the configuration of our enclave service. type Config struct { + // AppCmd can be set to the command that starts the enclave application. + // For example: + // + // nc -l -p 1234 + // + // Veil starts the given application after its internal Web server is + // running, and subsequently waits for the application to finish. When the + // application stops or crashes, veil terminates. + AppCmd string + // AppWebSrv should be set to the enclave-internal Web server of the // enclave application, e.g., "http://127.0.0.1:8080". Nitriding acts as a // TLS-terminating reverse proxy and forwards incoming HTTP requests to diff --git a/internal/httputil/httputil.go b/internal/httputil/httputil.go index 0a8d25f..900055a 100644 --- a/internal/httputil/httputil.go +++ b/internal/httputil/httputil.go @@ -1,6 +1,7 @@ package httputil import ( + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -10,6 +11,7 @@ import ( "encoding/base64" "encoding/pem" "errors" + "log" "math/big" "net/http" "time" @@ -24,9 +26,10 @@ const ( ) var ( - errBadForm = errors.New("failed to parse POST form data") - errNoNonce = errors.New("could not find nonce in URL query parameters") - errBadNonceFormat = errors.New("unexpected nonce format; must be Base64 string") + errBadForm = errors.New("failed to parse POST form data") + errNoNonce = errors.New("could not find nonce in URL query parameters") + errBadNonceFormat = errors.New("unexpected nonce format; must be Base64 string") + errDeadlineExceeded = errors.New("deadline exceeded") ) // ExtractNonce extracts a nonce from the HTTP request's parameters, e.g.: @@ -72,6 +75,39 @@ func NewNoAuthHTTPClient() *http.Client { } } +func WaitForSvc( + ctx context.Context, + client *http.Client, + url string, +) (err error) { + defer errs.Wrap(&err, "failed to wait for service") + + start := time.Now() + deadline, ok := ctx.Deadline() + if !ok { + return errors.New("context has no deadline") + } + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return err + } + req = req.WithContext(ctx) + + for { + log.Print("Making request to service...") + if _, err := client.Do(req); err == nil { + log.Print("Service is ready.") + return nil + } + if time.Since(start) > deadline.Sub(start) { + log.Printf("%v > %v", time.Since(start), deadline.Sub(start)) + return errDeadlineExceeded + } + time.Sleep(10 * time.Millisecond) + } +} + // CreateCertificate creates a self-signed certificate and returns the // PEM-encoded certificate and key. Some of the code below was taken from: // https://eli.thegreenplace.net/2021/go-https-servers-with-tls/ diff --git a/internal/httputil/httputil_test.go b/internal/httputil/httputil_test.go index 3e6e0ce..234b48d 100644 --- a/internal/httputil/httputil_test.go +++ b/internal/httputil/httputil_test.go @@ -1,9 +1,12 @@ package httputil import ( + "context" "net/http" + "net/http/httptest" "net/url" "testing" + "time" "github.com/stretchr/testify/require" @@ -12,6 +15,48 @@ import ( "github.com/Amnesic-Systems/veil/internal/util" ) +func TestWaitForSvc(t *testing.T) { + ctx := context.Background() + deadline := 500 * time.Millisecond + + cases := []struct { + name string + unresponsive bool + wantErr error + }{ + { + name: "unresponsive web server", + unresponsive: true, + wantErr: errDeadlineExceeded, + }, + { + name: "responsive web server", + }, + } + + newWebSrv := func(unresponsive bool) *httptest.Server { + return httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if unresponsive { + <-r.Context().Done() + } + w.WriteHeader(http.StatusOK) + }), + ) + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + websrv := newWebSrv(c.unresponsive) + defer websrv.Close() + + ctx, cancelFunc := context.WithDeadline(ctx, time.Now().Add(deadline)) + defer cancelFunc() + require.ErrorIs(t, WaitForSvc(ctx, websrv.Client(), websrv.URL), c.wantErr) + }) + } +} + func TestExtractNonce(t *testing.T) { cases := []struct { name string From fa277c50e7a981ce37b6971e8eb42152bab7b92a Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Thu, 21 Nov 2024 07:31:31 -0600 Subject: [PATCH 2/9] Rename `httputil` to `httpx`. --- cmd/veil-verify/main.go | 4 ++-- cmd/veil/main.go | 4 ++-- cmd/veil/main_test.go | 14 +++----------- internal/{httputil/httputil.go => httpx/httpx.go} | 6 +++--- .../httputil_test.go => httpx/httpx_test.go} | 2 +- internal/service/handle/encode.go | 4 ++-- internal/service/handle/handlers.go | 4 ++-- internal/service/service.go | 4 ++-- 8 files changed, 17 insertions(+), 25 deletions(-) rename internal/{httputil/httputil.go => httpx/httpx.go} (96%) rename internal/{httputil/httputil_test.go => httpx/httpx_test.go} (99%) diff --git a/cmd/veil-verify/main.go b/cmd/veil-verify/main.go index 604ba4f..be76668 100644 --- a/cmd/veil-verify/main.go +++ b/cmd/veil-verify/main.go @@ -20,7 +20,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" "github.com/Amnesic-Systems/veil/internal/errs" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/nonce" "github.com/Amnesic-Systems/veil/internal/util" ) @@ -153,7 +153,7 @@ func attestEnclave(ctx context.Context, cfg *config) (err error) { // Request the enclave's attestation document. We don't verify HTTPS // certificates because authentication is happening via the attestation // document. - client := httputil.NewNoAuthHTTPClient() + client := httpx.NewUnauthClient() url := cfg.addr + "/enclave/attestation?nonce=" + nonce.URLEncode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { diff --git a/cmd/veil/main.go b/cmd/veil/main.go index 24f1277..80dd3a7 100644 --- a/cmd/veil/main.go +++ b/cmd/veil/main.go @@ -20,7 +20,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" "github.com/Amnesic-Systems/veil/internal/errs" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/service" "github.com/Amnesic-Systems/veil/internal/tunnel" "github.com/Amnesic-Systems/veil/internal/util" @@ -160,7 +160,7 @@ func eventuallyRunAppCmd(ctx context.Context, cfg *config.Config, cmd string) (e deadlineCtx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Second)) defer cancel() url := fmt.Sprintf("http://localhost:%d", cfg.IntPort) - if err := httputil.WaitForSvc(deadlineCtx, httputil.NewNoAuthHTTPClient(), url); err != nil { + if err := httpx.WaitForSvc(deadlineCtx, httpx.NewUnauthClient(), url); err != nil { return err } log.Print("Internal service ready; running app command.") diff --git a/cmd/veil/main_test.go b/cmd/veil/main_test.go index ca550b2..b25b7aa 100644 --- a/cmd/veil/main_test.go +++ b/cmd/veil/main_test.go @@ -23,7 +23,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" "github.com/Amnesic-Systems/veil/internal/httperr" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/nonce" "github.com/Amnesic-Systems/veil/internal/service/attestation" "github.com/Amnesic-Systems/veil/internal/testutil" @@ -60,22 +60,14 @@ func startSvc(t *testing.T, cfg []string) func() { // Block until the services are ready. deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) defer cancelFunc() - if err := httputil.WaitForSvc( - deadline, - httputil.NewNoAuthHTTPClient(), - intSrv("/"), - ); err != nil { + if err := httpx.WaitForSvc(deadline, httpx.NewUnauthClient(), intSrv("/")); err != nil { t.Logf("error waiting for internal service: %v", err) return f } if !slices.Contains(cfg, "-wait-for-app") { deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) defer cancelFunc() - if err := httputil.WaitForSvc( - deadline, - httputil.NewNoAuthHTTPClient(), - extSrv("/"), - ); err != nil { + if err := httpx.WaitForSvc(deadline, httpx.NewUnauthClient(), extSrv("/")); err != nil { t.Logf("error waiting for external service: %v", err) return f } diff --git a/internal/httputil/httputil.go b/internal/httpx/httpx.go similarity index 96% rename from internal/httputil/httputil.go rename to internal/httpx/httpx.go index 900055a..ee0e4b3 100644 --- a/internal/httputil/httputil.go +++ b/internal/httpx/httpx.go @@ -1,4 +1,4 @@ -package httputil +package httpx import ( "context" @@ -59,11 +59,11 @@ func ExtractNonce(r *http.Request) (n *nonce.Nonce, err error) { return n, nil } -// NewNoAuthHTTPClient returns an HTTP client that skips HTTPS certificate +// NewUnauthClient returns an HTTP client that skips HTTPS certificate // validation. In the context of veil, this is fine because all we need is a // confidential channel; not an authenticated channel. Authentication is // handled by the next layer, using attestation documents. -func NewNoAuthHTTPClient() *http.Client { +func NewUnauthClient() *http.Client { transport := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, diff --git a/internal/httputil/httputil_test.go b/internal/httpx/httpx_test.go similarity index 99% rename from internal/httputil/httputil_test.go rename to internal/httpx/httpx_test.go index 234b48d..4ae877d 100644 --- a/internal/httputil/httputil_test.go +++ b/internal/httpx/httpx_test.go @@ -1,4 +1,4 @@ -package httputil +package httpx import ( "context" diff --git a/internal/service/handle/encode.go b/internal/service/handle/encode.go index 114f9de..415fe7d 100644 --- a/internal/service/handle/encode.go +++ b/internal/service/handle/encode.go @@ -8,7 +8,7 @@ import ( "net/http" "github.com/Amnesic-Systems/veil/internal/httperr" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/service/attestation" ) @@ -33,7 +33,7 @@ func encodeAndAttest[T any]( // Try to extract the client's nonce from the request. If this fails, abort // attestation because the client no longer has a way to verify the // attestation document's freshness. - n, err := httputil.ExtractNonce(r) + n, err := httpx.ExtractNonce(r) if err != nil { log.Println(err) encode(w, http.StatusBadRequest, httperr.New("found no valid nonce in HTTP request")) diff --git a/internal/service/handle/handlers.go b/internal/service/handle/handlers.go index 0c9b048..97df8d6 100644 --- a/internal/service/handle/handlers.go +++ b/internal/service/handle/handlers.go @@ -11,7 +11,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/addr" "github.com/Amnesic-Systems/veil/internal/config" "github.com/Amnesic-Systems/veil/internal/httperr" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/service/attestation" "github.com/Amnesic-Systems/veil/internal/util" ) @@ -132,7 +132,7 @@ func Attestation( builder *attestation.Builder, ) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - n, err := httputil.ExtractNonce(r) + n, err := httpx.ExtractNonce(r) if err != nil { encode(w, http.StatusBadRequest, httperr.New(err.Error())) return diff --git a/internal/service/service.go b/internal/service/service.go index 79fd0f5..2799c77 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -15,7 +15,7 @@ import ( "github.com/Amnesic-Systems/veil/internal/config" "github.com/Amnesic-Systems/veil/internal/enclave" "github.com/Amnesic-Systems/veil/internal/errs" - "github.com/Amnesic-Systems/veil/internal/httputil" + "github.com/Amnesic-Systems/veil/internal/httpx" "github.com/Amnesic-Systems/veil/internal/service/attestation" "github.com/Amnesic-Systems/veil/internal/system" "github.com/Amnesic-Systems/veil/internal/tunnel" @@ -40,7 +40,7 @@ func Run( } // Create a TLS certificate for the external Web server. - cert, key, err := httputil.CreateCertificate(config.FQDN) + cert, key, err := httpx.CreateCertificate(config.FQDN) if err != nil { log.Fatalf("Failed to create certificate: %v", err) } From 3acf6676164ac97652de5d93398eb784b91f8366 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 07:33:39 -0600 Subject: [PATCH 3/9] Unconditionally exit after application is done. After the application is done, we want to exit regardless of if the application succeeded or not. --- cmd/veil/main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/veil/main.go b/cmd/veil/main.go index 80dd3a7..3cd16ad 100644 --- a/cmd/veil/main.go +++ b/cmd/veil/main.go @@ -136,9 +136,10 @@ func run(ctx context.Context, out io.Writer, args []string) (err error) { go func() { if err := eventuallyRunAppCmd(ctx, cfg, cfg.AppCmd); err != nil { log.Printf("App unavailable: %v", err) - // Shut down the service if the app command fails. - cancel() } + // Shut down the service if the app command has terminated, + // successfully or not. + cancel() }() } From 6da96917339e6d61034a90dc7e6182f6dfd5a138 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 07:51:25 -0600 Subject: [PATCH 4/9] Refactor automation and add unit test. --- cmd/veil/main_test.go | 78 +++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/cmd/veil/main_test.go b/cmd/veil/main_test.go index b25b7aa..37443d7 100644 --- a/cmd/veil/main_test.go +++ b/cmd/veil/main_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/Amnesic-Systems/veil/internal/addr" + "github.com/Amnesic-Systems/veil/internal/config" "github.com/Amnesic-Systems/veil/internal/enclave" "github.com/Amnesic-Systems/veil/internal/enclave/nitro" "github.com/Amnesic-Systems/veil/internal/enclave/noop" @@ -40,14 +41,13 @@ func withFlags(flag ...string) []string { return append(f, flag...) } -func startSvc(t *testing.T, cfg []string) func() { +func startSvc(t *testing.T, cfg []string) ( + context.CancelFunc, + *sync.WaitGroup, +) { var ( - ctx, cancelCtx = context.WithCancel(context.Background()) - wg = new(sync.WaitGroup) - f = func() { - cancelCtx() - wg.Wait() - } + ctx, cancel = context.WithCancel(context.Background()) + wg = new(sync.WaitGroup) ) wg.Add(1) @@ -55,30 +55,34 @@ func startSvc(t *testing.T, cfg []string) func() { defer wg.Done() // run blocks until the context is cancelled. assert.NoError(t, run(ctx, os.Stderr, cfg)) + cancel() }(ctx, wg) // Block until the services are ready. - deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) - defer cancelFunc() + deadline, cancelDl := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancelDl() if err := httpx.WaitForSvc(deadline, httpx.NewUnauthClient(), intSrv("/")); err != nil { t.Logf("error waiting for internal service: %v", err) - return f + return cancel, wg } if !slices.Contains(cfg, "-wait-for-app") { - deadline, cancelFunc := context.WithDeadline(ctx, time.Now().Add(time.Second)) - defer cancelFunc() + deadline, cancelDl := context.WithDeadline(ctx, time.Now().Add(time.Second)) + defer cancelDl() if err := httpx.WaitForSvc(deadline, httpx.NewUnauthClient(), extSrv("/")); err != nil { t.Logf("error waiting for external service: %v", err) - return f + return cancel, wg } } + return cancel, wg +} - // Return function that shuts down the service. - return f +func waitForSvc(_ context.CancelFunc, wg *sync.WaitGroup) { + wg.Wait() } -func stopSvc(stop func()) { - stop() +func stopSvc(cancel context.CancelFunc, wg *sync.WaitGroup) { + cancel() + wg.Wait() } func intSrv(path string) string { @@ -405,3 +409,43 @@ func TestReverseProxy(t *testing.T) { }) } } + +func TestRunApp(t *testing.T) { + fd, err := os.CreateTemp("", "veil-test") + require.NoError(t, err) + defer os.Remove(fd.Name()) + + cases := []struct { + name string + command string + }{ + { + name: "curl", + // Run curl to fetch veil's configuration from its external Web + // server. + command: fmt.Sprintf("curl --silent --insecure --output %s "+ + "https://localhost:%d/enclave/config?nonce=%s", + fd.Name(), + defaultExtPort, + util.Must(nonce.New()).URLEncode(), + ), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + waitForSvc(startSvc(t, withFlags("-app-cmd", c.command, "-insecure"))) + + // Read curl's output, which should be our JSON-encoded + // configuration file. + content, err := io.ReadAll(fd) + require.NoError(t, err) + + // Decode the configuration file and verify that the application + // command is identical to what we just ran. + var cfg config.Config + require.NoError(t, json.Unmarshal(content, &cfg)) + require.Equal(t, c.command, cfg.AppCmd) + }) + } +} From 1584aebfdbe5368c5e1820ed6b0ec19a079403ff Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 07:52:24 -0600 Subject: [PATCH 5/9] Add log message. --- internal/service/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/service.go b/internal/service/service.go index 2799c77..d05fcc5 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -136,6 +136,7 @@ func startAllWebSrvs( // Wait until the context is canceled, at which point it's time to stop web // servers. <-ctx.Done() + log.Print("Context cancelled; shutting down veil.") if err := intSrv.Shutdown(ctx); err != nil { log.Printf("Error shutting down internal server: %v", err) } From 88b773028bafc6c99e2c40a038b4ae983cede3d6 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 08:02:59 -0600 Subject: [PATCH 6/9] Elaborate on misleading comment. --- cmd/veil/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/veil/main.go b/cmd/veil/main.go index 3cd16ad..538fbb8 100644 --- a/cmd/veil/main.go +++ b/cmd/veil/main.go @@ -173,7 +173,8 @@ func runAppCmd(ctx context.Context, cmdStr string) error { args := strings.Split(cmdStr, " ") cmd := exec.CommandContext(ctx, args[0], args[1:]...) - // Print the enclave application's stdout and stderr. + // Discard the enclave application's stdout and stderr. Regardless, we have + // to consume its output to prevent the application from blocking. appStderr, err := cmd.StderrPipe() if err != nil { return err From 083245464ff06b40ffd51240b7886029d1f1b4c0 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 08:04:33 -0600 Subject: [PATCH 7/9] Simplify code. --- cmd/veil/main.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cmd/veil/main.go b/cmd/veil/main.go index 538fbb8..f61106b 100644 --- a/cmd/veil/main.go +++ b/cmd/veil/main.go @@ -192,12 +192,8 @@ func runAppCmd(ctx context.Context, cmdStr string) error { return err } log.Println("Waiting for application to terminate.") - if err := cmd.Wait(); err != nil { - return err - } - log.Println("Application terminated.") - - return nil + defer log.Println("Application terminated.") + return cmd.Wait() } func forward(from io.Reader, to io.Writer) { From aa8af19e1bc9952efea73903321de32bef5c85d1 Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 08:07:22 -0600 Subject: [PATCH 8/9] Remove unnecessary log message. --- internal/httpx/httpx.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/httpx/httpx.go b/internal/httpx/httpx.go index ee0e4b3..3b520d7 100644 --- a/internal/httpx/httpx.go +++ b/internal/httpx/httpx.go @@ -101,7 +101,6 @@ func WaitForSvc( return nil } if time.Since(start) > deadline.Sub(start) { - log.Printf("%v > %v", time.Since(start), deadline.Sub(start)) return errDeadlineExceeded } time.Sleep(10 * time.Millisecond) From e8120e90709291a0cffa2c10bc652912cb5d590a Mon Sep 17 00:00:00 2001 From: Philipp Winter Date: Fri, 22 Nov 2024 08:11:40 -0600 Subject: [PATCH 9/9] Add function comment. --- internal/httpx/httpx.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/httpx/httpx.go b/internal/httpx/httpx.go index 3b520d7..a22c30a 100644 --- a/internal/httpx/httpx.go +++ b/internal/httpx/httpx.go @@ -75,6 +75,10 @@ func NewUnauthClient() *http.Client { } } +// WaitForSvc waits for the service (specified by the URL) to become available +// by making repeated HTTP GET requests using the given HTTP client. This +// function blocks until 1) the service responds with an HTTP response or 2) the +// given context expires. func WaitForSvc( ctx context.Context, client *http.Client,