diff --git a/vsock_sample/go/Dockerfile.client b/vsock_sample/go/Dockerfile.client new file mode 100644 index 00000000..9115ad86 --- /dev/null +++ b/vsock_sample/go/Dockerfile.client @@ -0,0 +1,17 @@ +FROM golang:1.19-alpine AS builder + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY *.go ./ + +RUN CGO_ENABLED=0 GOOS=linux go build -o vsock-sample . + +FROM scratch + +COPY --from=builder /app/vsock-sample . + +ENTRYPOINT ["/vsock-sample", "client"] diff --git a/vsock_sample/go/Dockerfile.server b/vsock_sample/go/Dockerfile.server new file mode 100644 index 00000000..1aab941b --- /dev/null +++ b/vsock_sample/go/Dockerfile.server @@ -0,0 +1,17 @@ +FROM golang:1.19-alpine AS builder + +WORKDIR /app + +COPY go.mod ./ +COPY go.sum ./ +RUN go mod download + +COPY *.go ./ + +RUN CGO_ENABLED=0 GOOS=linux go build -o vsock-sample . + +FROM scratch + +COPY --from=builder /app/vsock-sample . + +CMD ["/vsock-sample", "server", "-port", "5005"] \ No newline at end of file diff --git a/vsock_sample/go/README.md b/vsock_sample/go/README.md new file mode 100644 index 00000000..1f7e7311 --- /dev/null +++ b/vsock_sample/go/README.md @@ -0,0 +1,114 @@ +# Vsock Communication Sample + +A hello-world example for Nitro Enclaves vsock server and client communication. + +## Prerequisites + +1. The `vsock-sample` is a Nitro Enclaves application that can be run either +as a server or a client. The client sends the “Hello, world!” message to the +server. The server receives the message and prints it to the standard output. + +2. The sample is written and tested in Go 1.19. Any Go version 1.17+ should work with this example (as the vsock package dependency is written in 1.17). + +## How to use the enclave as the server and the parent instance as the client + +1. Build the Enclave Image File (EIF) starting from the `Dockerfile.server` file +in this directory. We chose to use a multi-stage build to build the Docker image +from *go-alpine* and then deploy the application using a *scratch* image to keep +the enclave image as small as possible, but you can also use other Docker images. + +__Note__: You can use any other port number besides 5005 by modifying the command +inside the `Dockerfile.server` file. + +``` +docker build -t vsock-sample-server -f Dockerfile.server . +nitro-cli build-enclave --docker-uri vsock-sample-server --output-file vsock_sample_server.eif +``` + +2. Configure the pool of memory and vCPUs (the `nitro-cli-config` script can be used) +and run the enclave using the previously-built EIF. + +``` +// 1 vCPUs and 128 MiB memory +nitro-cli-config -t 1 -m 128 +nitro-cli run-enclave --eif-path vsock_sample_server.eif --cpu-count 1 --memory 128 --debug-mode +``` + +3. Connect to the enclave console using `nitro-cli`. + +``` +nitro-cli console --enclave-id $ENCLAVE_ID +``` + +4. In another terminal, build and run the client. + +``` +docker build -t vsock-sample-client -f Dockerfile.client . +docker run -it --rm vsock-sample-client -cid $ENCLAVE_CID -port 5005 +``` + +__Note__: Here `$ENCLAVE_CID` is a generated integer value (e.g. 16) of the enclave CID. + +5. The enclave console output should look like this: + +``` +[ 0.051633] rtc-pl031 40002000.rtc: setting system clock to 2022-12-08 20:21:47 UTC (1670530907) +[ 0.052366] Freeing unused kernel memory: 512K +[ 0.056733] nsm: loading out-of-tree module taints kernel. +[ 0.057098] nsm: module verification failed: signature and/or required key missing - tainting kernel +[ 0.058013] NSM RNG: returning rand bytes = 16 +[ 0.058493] NSM RNG: returning rand bytes = 128 +[ 0.058803] random: fast init done +[ 0.059766] NSM RNG: returning rand bytes = 128 +[ 0.060030] random: crng init done +Listening on :5005 +2022/12/08 20:22:43 [INFO]: Hello, world! +``` + +## How to use the parent instance as the server and the enclave as the client + +1. Build the Enclave Image File (EIF) starting from the `Dockerfile.client` file +in this directory. We chose to use a multi-stage build to build the Docker image +from *go-alpine* and then deploy the application using a *scratch* image to keep +the enclave image as small as possible, but you can also use other Docker images. + +__Notes__: +* The value 3 is the CID of the parent instance +* You can use any other port number besides 5005 by modifying the command inside the Dockerfile.client file + +``` +docker build -t vsock-sample-client -f Dockerfile.client . +``` + +``` +nitro-cli build-enclave --docker-uri vsock-sample-client --output-file vsock_sample_client.eif +``` + +2. Build and run the server inside the parent instance. + +``` +docker build -t vsock-sample-server -f Dockerfile.server . +docker run -itd --name vsock-sample-server vsock-sample-server +``` + +3. Configure the pool of memory and vCPUs (the `nitro-cli-config` +script can be used) and run the enclave using the built EIF. + +``` +// 2 vCPUs and 128 MiB memory +nitro-cli-config -t 1 -m 128 +nitro-cli run-enclave --eif-path vsock_sample_client.eif --cpu-count 1 --memory 128 --debug-mode +``` + +4. The server container output should print __Hello, world!__. + +``` +docker logs vsock-sample-server +Listening on :5005 +[INFO]: Hello, world! +``` + +__Note__: +* The client application inside the enclave sends the message once and then exits thus the enclave shall terminate. + +Now you can replace the client/server code with your own code. diff --git a/vsock_sample/go/go.mod b/vsock_sample/go/go.mod new file mode 100644 index 00000000..9eb16e5f --- /dev/null +++ b/vsock_sample/go/go.mod @@ -0,0 +1,10 @@ +module github.com/aws-samples/aws-nitro-enclaves-samples/vsock_sample/go + +go 1.19 + +require github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 + +require ( + github.com/pkg/errors v0.8.1-0.20170910134614-2b3a18b5f0fb // indirect + golang.org/x/sys v0.2.0 // indirect +) diff --git a/vsock_sample/go/go.sum b/vsock_sample/go/go.sum new file mode 100644 index 00000000..4c61698e --- /dev/null +++ b/vsock_sample/go/go.sum @@ -0,0 +1,6 @@ +github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 h1:DZMFueDbfz6PNc1GwDRA8+6lBx1TB9UnxDQliCqR73Y= +github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2/go.mod h1:SWzULI85WerrFt3u+nIm5F9l7EvxZTKQvd0InF3nmgM= +github.com/pkg/errors v0.8.1-0.20170910134614-2b3a18b5f0fb h1:CKWls8QOVQs/qmuUuGOeHMpIqSx6f9S72udJ48vEeKo= +github.com/pkg/errors v0.8.1-0.20170910134614-2b3a18b5f0fb/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/vsock_sample/go/vsock-sample.go b/vsock_sample/go/vsock-sample.go new file mode 100644 index 00000000..968b1771 --- /dev/null +++ b/vsock_sample/go/vsock-sample.go @@ -0,0 +1,84 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "log" + "net" + "os" + "path/filepath" + + "github.com/linuxkit/virtsock/pkg/vsock" +) + +func main() { + var showVersion bool + flag.BoolVar(&showVersion, "version", false, "Prints version information.") + + clientCmd := flag.NewFlagSet("client", flag.ExitOnError) + cCid := clientCmd.Int("cid", 3, "The remote endpoint CID.") + cPort := clientCmd.Int("port", 5005, "The remote endpoint port.") + + serverCmd := flag.NewFlagSet("server", flag.ExitOnError) + sPort := serverCmd.Uint("port", 5005, "The local port to listen on.") + + switch os.Args[1] { + case "client": + clientCmd.Parse(os.Args[2:]) + fmt.Printf("CID:\t%d\nPort:\t%d\n", *cCid, *cPort) + + conn, err := vsock.Dial(uint32(*cCid), uint32(*cPort)) + if err != nil { + log.Fatalf("failed to connect: %v", err) + } + defer conn.Close() + + message := "Hello, world!" + w := bufio.NewWriter(conn) + n, merr := w.WriteString(message) + if merr != nil { + log.Fatalf("failed to write message: %v", merr) + } + w.Flush() + fmt.Printf("Wrote %d bytes to connection: %s\n", n, message) + + case "server": + serverCmd.Parse(os.Args[2:]) + + lis, err := vsock.Listen(vsock.CIDAny, uint32(*sPort)) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + defer lis.Close() + fmt.Printf("Listening on :%d\n", *sPort) + for { + conn, err := lis.Accept() + if err != nil { + fmt.Println("Error accepting", err.Error()) + os.Exit(1) + } + defer conn.Close() + + go func(c net.Conn) { + r := bufio.NewReader(c) + data, err := r.ReadString('\n') + if err != nil && err != io.EOF { + log.Println("[ERROR]: couldn't read from connection:", err.Error()) + return + } + log.Printf("[INFO]: %s\n", data) + }(conn) + } + + default: + flag.Parse() + + if showVersion { + fmt.Printf("%s 0.1.0\n", filepath.Base(os.Args[0])) + os.Exit(0) + } + } + +}