diff --git a/rune/attest.go b/rune/attest.go new file mode 100644 index 0000000000000000000000000000000000000000..e09e30a184c4da7eb86fd571b0d28a41fa8803f3 --- /dev/null +++ b/rune/attest.go @@ -0,0 +1,185 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/opencontainers/runc/libcontainer" + "github.com/opencontainers/runc/libcontainer/utils" + "github.com/opencontainers/runc/libenclave/attestation/sgx" + _ "github.com/opencontainers/runc/libenclave/attestation/sgx/ias" + "github.com/opencontainers/runc/libenclave/intelsgx" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/urfave/cli" +) + +const ( + envSeparator = "=" +) + +var attestCommand = cli.Command{ + Name: "attest", + Usage: "attest generates a remote attestation to the corresponding enclave container", + ArgsUsage: ` [command options] +Where "" is the name for the instance of the container`, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "product", + Usage: "specify whether using production attestation service", + }, + cli.StringFlag{ + Name: "spid", + Usage: "specify SPID", + }, + cli.StringFlag{ + Name: "subscription-key, -key", + Usage: "specify the subscription key", + }, + cli.BoolFlag{ + Name: "linkable", + Usage: "specify the EPID signatures policy type", + }, + }, + Action: func(context *cli.Context) error { + if err := revisePidFile(context); err != nil { + return err + } + status, err := attestProcess(context) + if err == nil { + os.Exit(status) + } + return fmt.Errorf("attest failed: %v", err) + }, + SkipArgReorder: true, +} + +func attestProcess(context *cli.Context) (int, error) { + container, err := getContainer(context) + if err != nil { + return -1, err + } + status, err := container.Status() + if err != nil { + return -1, err + } + if status == libcontainer.Stopped { + return -1, fmt.Errorf("cannot attest a container that has stopped") + } + + config := container.Config() + if config.Enclave == nil { + return -1, fmt.Errorf("Attest command: container.Config.Enclave is null") + } + + state, err := container.State() + if err != nil { + return -1, err + } + bundle := utils.SearchLabels(state.Config.Labels, "bundle") + p, err := getAttestProcess(context, bundle) + if err != nil { + return -1, err + } + + logLevel := "info" + if context.GlobalBool("debug") { + logLevel = "debug" + } + + r := &runner{ + enableSubreaper: false, + shouldDestroy: false, + container: container, + consoleSocket: context.String("console-socket"), + detach: false, + action: CT_ACT_RUN, + init: false, + preserveFDs: context.Int("preserve-fds"), + logLevel: logLevel, + } + return r.run(p) +} + +func getAttestProcess(context *cli.Context, bundle string) (*specs.Process, error) { + // process via cli flags + if err := os.Chdir(bundle); err != nil { + return nil, err + } + spec, err := loadSpec(specConfig) + if err != nil { + return nil, err + } + p := spec.Process + p.Args = context.Args()[1:] + // override the cwd, if passed + if context.String("cwd") != "" { + p.Cwd = context.String("cwd") + } + if ap := context.String("apparmor"); ap != "" { + p.ApparmorProfile = ap + } + if l := context.String("process-label"); l != "" { + p.SelinuxLabel = l + } + if caps := context.StringSlice("cap"); len(caps) > 0 { + for _, c := range caps { + p.Capabilities.Bounding = append(p.Capabilities.Bounding, c) + p.Capabilities.Inheritable = append(p.Capabilities.Inheritable, c) + p.Capabilities.Effective = append(p.Capabilities.Effective, c) + p.Capabilities.Permitted = append(p.Capabilities.Permitted, c) + p.Capabilities.Ambient = append(p.Capabilities.Ambient, c) + } + } + // append the passed env variables + p.Env = append(p.Env, "SPID"+envSeparator+context.String("spid")) + p.Env = append(p.Env, "SUBSCRIPTION_KEY"+envSeparator+context.String("subscription-key")) + + isProductEnclave := strconv.Itoa(int(sgx.DebugEnclave)) + if context.Bool("product") { + isProductEnclave = strconv.Itoa(int(sgx.ProductEnclave)) + } + p.Env = append(p.Env, "PRODUCT"+envSeparator+isProductEnclave) + + quoteType := strconv.Itoa(int(intelsgx.QuoteSignatureTypeUnlinkable)) + if context.Bool("linkable") { + quoteType = strconv.Itoa(int(intelsgx.QuoteSignatureTypeLinkable)) + } + p.Env = append(p.Env, "QUOTE_TYPE"+envSeparator+quoteType) + + var AttestCommand string = "true" + p.Env = append(p.Env, "AttestCommand"+envSeparator+AttestCommand) + + // set the tty + p.Terminal = false + if context.IsSet("tty") { + p.Terminal = context.Bool("tty") + } + if context.IsSet("no-new-privs") { + p.NoNewPrivileges = context.Bool("no-new-privs") + } + // override the user, if passed + if context.String("user") != "" { + u := strings.SplitN(context.String("user"), ":", 2) + if len(u) > 1 { + gid, err := strconv.Atoi(u[1]) + if err != nil { + return nil, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err) + } + p.User.GID = uint32(gid) + } + uid, err := strconv.Atoi(u[0]) + if err != nil { + return nil, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err) + } + p.User.UID = uint32(uid) + } + for _, gid := range context.Int64Slice("additional-gids") { + if gid < 0 { + return nil, fmt.Errorf("additional-gids must be a positive number %d", gid) + } + p.User.AdditionalGids = append(p.User.AdditionalGids, uint32(gid)) + } + return p, validateAttestProcessSpec(p) +} diff --git a/rune/libenclave/agent.go b/rune/libenclave/agent.go index a3dd11c9fffa1fc8c0ca205ddae6e3c51c18a0a6..cd2f42e5e5b867214d76997dcf042e7e7efa7294 100644 --- a/rune/libenclave/agent.go +++ b/rune/libenclave/agent.go @@ -140,15 +140,7 @@ func handleRequest(conn net.Conn, id int) { var err error resp := &pb.AgentServiceResponse{} - resp.Exec = &pb.AgentServiceResponse_Execute{} exitCode := int32(1) - defer func() { - resp.Exec.ExitCode = exitCode - if err != nil { - resp.Exec.Error = fmt.Sprint(err) - } - protoBufWrite(conn, resp) - }() req := &pb.AgentServiceRequest{} if err = protoBufRead(conn, req); err != nil { @@ -166,6 +158,32 @@ func handleRequest(conn net.Conn, id int) { } defer connFile.Close() + if req.Attest != nil { + logrus.Infof("In function handleRequest: get a attest request") + resp.Attest = &pb.AgentServiceResponse_Attest{} + err = enclaveRuntime.LaunchAttestation(req.Attest.Spid, + req.Attest.SubscriptionKey, + req.Attest.Product, + req.Attest.QuoteType) + if err != nil { + resp.Attest.Error = fmt.Sprint(err) + } else { + exitCode = 0 + } + resp.Attest.ExitCode = exitCode + protoBufWrite(conn, resp) + return + } + + resp.Exec = &pb.AgentServiceResponse_Execute{} + defer func() { + resp.Exec.ExitCode = exitCode + if err != nil { + resp.Exec.Error = fmt.Sprint(err) + } + protoBufWrite(conn, resp) + }() + // Retrieve signal pipe. signalPipe, err := utils.RecvFd(connFile) if err != nil { diff --git a/rune/libenclave/proto/agent-service.proto b/rune/libenclave/proto/agent-service.proto index ed8dc1250b04b0a02e642823af734f8bd8aee11b..38ad66ecdffba6ef00ba4d2e432d5057a416e304 100644 --- a/rune/libenclave/proto/agent-service.proto +++ b/rune/libenclave/proto/agent-service.proto @@ -12,8 +12,16 @@ message AgentServiceRequest{ int32 sig = 1; } + message Attest { + string spid = 1; + string subscriptionKey = 2; + uint32 product = 3; + uint32 quoteType = 4; + } + Execute exec = 1; Kill kill = 2; + Attest attest = 3; } message AgentServiceResponse { @@ -22,5 +30,11 @@ message AgentServiceResponse { string error = 2; } + message Attest { + int32 exitCode = 1; + string error = 2; + } + Execute exec = 1; + Attest attest = 2; } diff --git a/rune/libenclave/runelet.go b/rune/libenclave/runelet.go index 2287ec89d599bde0de90f5d03c02690c688caf5d..a32f69c1e0d38a7ca458f3ea3172103df6d5ff0d 100644 --- a/rune/libenclave/runelet.go +++ b/rune/libenclave/runelet.go @@ -6,14 +6,17 @@ import ( "github.com/opencontainers/runc/libcontainer/utils" "github.com/opencontainers/runc/libenclave/attestation/sgx" "github.com/opencontainers/runc/libenclave/configs" + "github.com/opencontainers/runc/libenclave/intelsgx" "github.com/opencontainers/runc/libenclave/internal/runtime" pb "github.com/opencontainers/runc/libenclave/proto" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "io" "io/ioutil" + "net" "os" "os/signal" + "strconv" "strings" "syscall" ) @@ -100,13 +103,26 @@ func StartInitialization(cmd []string, cfg *RuneletConfig) (exitCode int32, err notifySignal := make(chan os.Signal, signalBufferSize) if fifoFd == -1 { - exitCode, err = remoteExec(agentPipe, cmd, notifySignal) - if err != nil { + AttestCommand := os.Getenv("AttestCommand") + if AttestCommand == "true" { + logrus.Infof("Get an attest command") + exitCode, err = remoteAttest(agentPipe, config, notifySignal) + if err != nil { + return exitCode, err + } + logrus.Debugf("remote attest normally exits") + return exitCode, err - } - logrus.Debug("remote exec normally exits") + } else { + logrus.Infof("Get an exec command") + exitCode, err = remoteExec(agentPipe, cmd, notifySignal) + if err != nil { + return exitCode, err + } + logrus.Debug("remote exec normally exits") - return exitCode, err + return exitCode, err + } } notifyExit := make(chan struct{}) @@ -201,6 +217,105 @@ func finalizeInitialization(fifoFd int) error { return nil } +func remoteAttest(agentPipe *os.File, config *configs.InitEnclaveConfig, notifySignal chan os.Signal) (exitCode int32, err error) { + logrus.Infof("preparing to remote Attest") + c, err := net.FileConn(agentPipe) + if err != nil { + return 1, err + } + defer c.Close() + conn, ok := c.(*net.UnixConn) + if !ok { + return 1, fmt.Errorf("casting to UnixConn faild") + } + + sgxEnclaveType := os.Getenv("PRODUCT") + isProductEnclave, err := strconv.ParseUint(sgxEnclaveType, 10, 32) + if err != nil { + return 1, fmt.Errorf("Invalid sgxEnclaveType Configuration, error = %v!\n", err) + } + if isProductEnclave != sgx.DebugEnclave && isProductEnclave != sgx.ProductEnclave { + return 1, fmt.Errorf("Unsupported is_product_enclave Configuration %v!\n", sgxEnclaveType) + } + + quoteType := os.Getenv("QUOTE_TYPE") + raEpidQuoteType, err := strconv.ParseUint(quoteType, 10, 32) + if err != nil { + return 1, fmt.Errorf("Invalid Quote Type Configuration, error = %v!\n", err) + } + if raEpidQuoteType != (intelsgx.QuoteSignatureTypeUnlinkable) && raEpidQuoteType != (intelsgx.QuoteSignatureTypeLinkable) { + return 1, fmt.Errorf("Unsupported Quote Type Configuration %v!\n", raEpidQuoteType) + } + + req := &pb.AgentServiceRequest{} + req.Attest = &pb.AgentServiceRequest_Attest{ + Spid: os.Getenv("SPID"), + SubscriptionKey: os.Getenv("SUBSCRIPTION_KEY"), + Product: (uint32)(isProductEnclave), + QuoteType: (uint32)(raEpidQuoteType), + } + + if err = protoBufWrite(conn, req); err != nil { + return 1, err + } + + agentFile, err := conn.File() + if err != nil { + return 1, err + } + defer agentFile.Close() + + // Send signal notification pipe. + childSignalPipe, parentSignalPipe, err := os.Pipe() + agentFile, err = conn.File() + if err != nil { + return 1, err + } + defer agentFile.Close() + + // Send signal notification pipe. + childSignalPipe, parentSignalPipe, err = os.Pipe() + if err != nil { + return 1, err + } + defer func() { + if err != nil { + childSignalPipe.Close() + } + parentSignalPipe.Close() + }() + + if err = utils.SendFd(agentFile, childSignalPipe.Name(), childSignalPipe.Fd()); err != nil { + return 1, err + } + + // Close the child signal pipe in parent side **after** sending all stdio fds to + // make sure the parent runelet has retrieved the child signal pipe. + childSignalPipe.Close() + + signal.Notify(notifySignal) + + notifyExit := make(chan struct{}) + sigForwarderExit := forwardSignalToParent(parentSignalPipe, notifySignal, notifyExit) + + resp := &pb.AgentServiceResponse{} + if err = protoBufRead(conn, resp); err != nil { + return 1, err + } + + notifyExit <- struct{}{} + logrus.Debug("awaiting for signal forwarder exiting ...") + <-sigForwarderExit + logrus.Debug("signal forwarder exited") + + if resp.Attest.Error == "" { + err = nil + } else { + err = fmt.Errorf(resp.Attest.Error) + } + return resp.Attest.ExitCode, err +} + func remoteExec(agentPipe *os.File, cmd []string, notifySignal chan os.Signal) (exitCode int32, err error) { c := strings.Join(cmd, " ") logrus.Debugf("preparing to remote exec %s", c) diff --git a/rune/main.go b/rune/main.go index 618d5f8b097409ea6372959e8ecdd17160c2d047..743266980e807a38f56ed8f189fb41825174853a 100644 --- a/rune/main.go +++ b/rune/main.go @@ -130,6 +130,7 @@ func main() { startCommand, stateCommand, updateCommand, + attestCommand, } app.Before = func(context *cli.Context) error { if !context.IsSet("root") && xdgRuntimeDir != "" { diff --git a/rune/utils_linux.go b/rune/utils_linux.go index 54021889b21b58e11672c9bc21b9237ae6503621..bae2d4d1442b08a911624ba023029cf3b02e81e4 100644 --- a/rune/utils_linux.go +++ b/rune/utils_linux.go @@ -399,6 +399,19 @@ func validateProcessSpec(spec *specs.Process) error { return nil } +func validateAttestProcessSpec(spec *specs.Process) error { + if spec.Cwd == "" { + return fmt.Errorf("Cwd property must not be empty") + } + if !filepath.IsAbs(spec.Cwd) { + return fmt.Errorf("Cwd must be an absolute path") + } + if spec.SelinuxLabel != "" && !selinux.GetEnabled() { + return fmt.Errorf("selinux label is specified in config, but selinux is disabled or not supported") + } + return nil +} + type CtAct uint8 const ( diff --git a/sgx-tools/gen-quote.go b/sgx-tools/gen-quote.go index 144a2b111381add48d835b524c7bd4ca5a726c85..ecec44e95ad45d733fd72ef0049f4252502d5382 100644 --- a/sgx-tools/gen-quote.go +++ b/sgx-tools/gen-quote.go @@ -34,7 +34,7 @@ For example, generate the quote file according to the given local report file: }, cli.BoolFlag{ Name: "linkable", - Usage: "linkable", + Usage: "specify the EPID signatures policy type", }, }, Action: func(context *cli.Context) error {