github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/tao/kvm_custom_factory.go (about)

     1  // Copyright (c) 2016, Google Inc.  All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tao
    16  
    17  import (
    18  	"crypto/sha256"
    19  	"errors"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"os"
    24  	"os/exec"
    25  	"path"
    26  	"strconv"
    27  	"syscall"
    28  	"time"
    29  
    30  	"github.com/golang/glog"
    31  	"github.com/jlmucb/cloudproxy/go/tao/auth"
    32  	"github.com/jlmucb/cloudproxy/go/util"
    33  )
    34  
    35  // A VmConfig contains the details needed to start a new custom VM.
    36  type VmConfig struct {
    37  	Name        string
    38  	KernelPath  string
    39  	InitRamPath string
    40  	DiskPath    string
    41  	Memory      int
    42  	// The socket on the host that will be connected to virtio-serial on the guest.
    43  	// This is used for stacked CP hosts on the VM to connect to the host CP.
    44  	SocketPath string
    45  	// The port on the host that will be forwarded to port 22 on the guest for SSH.
    46  	Port string
    47  }
    48  
    49  // A KvmCustomContainer represents a hosted program running as a VM on
    50  // KVM. It uses os/exec.Cmd to send commands to QEMU/KVM to start the VM.
    51  // This use of os/exec is to avoid having to rewrite or hook into
    52  // libvirt for now.
    53  type KvmCustomContainer struct {
    54  
    55  	// The spec from which this vm was created.
    56  	spec HostedProgramSpec
    57  
    58  	// Hash of the kernel image.
    59  	KernelHash []byte
    60  
    61  	// Hash fo the InitRam image.
    62  	InitRamHash []byte
    63  
    64  	// The factory responsible for the vm.
    65  	Factory *LinuxKVMCustomFactory
    66  
    67  	// Configuration details for VM, mostly obtained from the factory.
    68  	// TODO(kwalsh) what is a good description for this?
    69  	Cfg *VmConfig
    70  
    71  	// The underlying vm process.
    72  	QCmd *exec.Cmd
    73  
    74  	// A channel to be signaled when the vm is done.
    75  	Done chan bool
    76  }
    77  
    78  // WaitChan returns a chan that will be signaled when the hosted vm is done.
    79  func (kcc *KvmCustomContainer) WaitChan() <-chan bool {
    80  	return kcc.Done
    81  }
    82  
    83  // Kill sends a SIGKILL signal to a QEMU instance.
    84  func (kcc *KvmCustomContainer) Kill() error {
    85  	// Kill the qemu command directly.
    86  	// TODO(tmroeder): rewrite this using qemu's communication/management
    87  	// system; sending SIGKILL is definitely not the right way to do this.
    88  	return kcc.QCmd.Process.Kill()
    89  }
    90  
    91  // Start starts a QEMU/KVM CoreOS container using the command line.
    92  func (kcc *KvmCustomContainer) startVM() error {
    93  
    94  	cfg := kcc.Cfg
    95  	qemuProg := "qemu-system-x86_64"
    96  	qemuArgs := []string{"-name", cfg.Name,
    97  		"-m", strconv.Itoa(cfg.Memory),
    98  		// Networking.
    99  		"-net", "nic,vlan=0,model=virtio",
   100  		"-net", "user,vlan=0,hostfwd=tcp::" + kcc.spec.Args[2] + "-:22,hostname=" + cfg.Name,
   101  		// Tao communications through virtio-serial. With this
   102  		// configuration, QEMU waits for a server on cfg.SocketPath,
   103  		// then connects to it.
   104  		"-chardev", "socket,path=" + cfg.SocketPath + ",id=port0-char",
   105  		"-device", "virtio-serial",
   106  		"-device", "virtserialport,id=port1,name=tao,chardev=port0-char",
   107  		// The kernel and initram image to boot from.
   108  		"-kernel", cfg.KernelPath,
   109  		"-initrd", cfg.InitRamPath,
   110  	}
   111  
   112  	kcc.QCmd = exec.Command(qemuProg, qemuArgs...)
   113  	kcc.QCmd.Stdin = os.Stdin
   114  	kcc.QCmd.Stdout = os.Stdout
   115  	kcc.QCmd.Stderr = os.Stderr
   116  	// TODO(kwalsh) set up env, dir, and uid/gid.
   117  	return kcc.QCmd.Start()
   118  }
   119  
   120  // Stop sends a SIGSTOP signal to a docker container.
   121  func (kcc *KvmCustomContainer) Stop() error {
   122  	// Stop the QEMU/KVM process with SIGSTOP.
   123  	// TODO(tmroeder): rewrite this using qemu's communication/management
   124  	// system; sending SIGSTOP is definitely not the right way to do this.
   125  	return kcc.QCmd.Process.Signal(syscall.SIGSTOP)
   126  }
   127  
   128  // Pid returns a numeric ID for this container.
   129  func (kcc *KvmCustomContainer) Pid() int {
   130  	return kcc.QCmd.Process.Pid
   131  }
   132  
   133  // ExitStatus returns an exit code for the container.
   134  func (kcc *KvmCustomContainer) ExitStatus() (int, error) {
   135  	s := kcc.QCmd.ProcessState
   136  	if s == nil {
   137  		return -1, fmt.Errorf("Child has not exited")
   138  	}
   139  	if code, ok := (*s).Sys().(syscall.WaitStatus); ok {
   140  		return int(code), nil
   141  	}
   142  	return -1, fmt.Errorf("Couldn't get exit status\n")
   143  }
   144  
   145  // A LinuxKVMCustomFactory manages hosted programs started as QEMU/KVM instances.
   146  type LinuxKVMCustomFactory struct {
   147  	Cfg *VmConfig
   148  }
   149  
   150  // NewLinuxKVMCustomFactory returns a new HostedProgramFactory that can
   151  // create docker containers to wrap programs.
   152  func NewLinuxKVMCustomFactory(cfg *VmConfig) HostedProgramFactory {
   153  	return &LinuxKVMCustomFactory{
   154  		Cfg: cfg,
   155  	}
   156  }
   157  
   158  // MakeSubprin computes the hash of a QEMU/KVM CoreOS image to get a
   159  // subprincipal for authorization purposes.
   160  func (lkcf *LinuxKVMCustomFactory) NewHostedProgram(spec HostedProgramSpec) (child HostedProgram, err error) {
   161  	// TODO(tmroeder): the combination of TeeReader and ReadAll doesn't seem
   162  	// to copy the entire image, so we're going to hash in place for now.
   163  	// This needs to be fixed to copy the image so we can avoid a TOCTTOU
   164  	// attack.
   165  
   166  	// The spec args must contain the kernel and initram paths as well as the port to use for SSH.
   167  	if len(spec.Args) != 3 {
   168  		glog.Errorf("Expected %d args, but got %d", 3, len(spec.Args))
   169  		for i, a := range spec.Args {
   170  			glog.Errorf("Arg %d: %s", i, a)
   171  		}
   172  		err = errors.New("KVM Custom guest Tao requires args: <kernel image> <initram image> <SSH port>")
   173  		return
   174  	}
   175  
   176  	b, err := ioutil.ReadFile(spec.Args[0])
   177  	if err != nil {
   178  		return
   179  	}
   180  	h1 := sha256.Sum256(b)
   181  
   182  	b, err = ioutil.ReadFile(spec.Args[1])
   183  	if err != nil {
   184  		return
   185  	}
   186  	h2 := sha256.Sum256(b)
   187  
   188  	sockName := getRandomFileName(nameLen)
   189  	sockPath := path.Join(lkcf.Cfg.SocketPath, sockName)
   190  
   191  	cfg := VmConfig{
   192  		Name:        getRandomFileName(nameLen),
   193  		KernelPath:  spec.Args[0],
   194  		InitRamPath: spec.Args[1],
   195  		Memory:      lkcf.Cfg.Memory,
   196  		SocketPath:  sockPath,
   197  		Port:        spec.Args[2],
   198  	}
   199  
   200  	child = &KvmCustomContainer{
   201  		spec:        spec,
   202  		KernelHash:  h1[:],
   203  		InitRamHash: h2[:],
   204  		Factory:     lkcf,
   205  		Done:        make(chan bool, 1),
   206  		Cfg:         &cfg,
   207  	}
   208  	return
   209  }
   210  
   211  // Subprin returns the subprincipal representing the hosted vm.
   212  func (kcc *KvmCustomContainer) Subprin() auth.SubPrin {
   213  	subprin := FormatCustomVmSubprin(kcc.spec.Id, kcc.KernelHash, kcc.InitRamHash)
   214  	return subprin
   215  }
   216  
   217  // FormatCustomVmSubprin produces a subprincipal with the given ID and hash.
   218  func FormatCustomVmSubprin(id uint, kernelHash []byte, initramHash []byte) auth.SubPrin {
   219  	var args []auth.Term
   220  	if id != 0 {
   221  		args = append(args, auth.Int(id))
   222  	}
   223  	args = append(args, auth.Bytes(kernelHash), auth.Bytes(initramHash))
   224  	return auth.SubPrin{auth.PrinExt{Name: "CustomVM", Arg: args}}
   225  }
   226  
   227  // Spec returns the specification used to start the hosted vm.
   228  func (kcc *KvmCustomContainer) Spec() HostedProgramSpec {
   229  	return kcc.spec
   230  }
   231  
   232  // Start launches a QEMU/KVM CoreOS instance, connects to it with SSH to start
   233  // the LinuxHost on it, and returns the socket connection to that host.
   234  func (kcc *KvmCustomContainer) Start() (channel io.ReadWriteCloser, err error) {
   235  
   236  	// Create the listening server before starting the connection. This lets
   237  	// QEMU start right away. See the comments in Start, above, for why this
   238  	// is.
   239  	channel = util.NewUnixSingleReadWriteCloser(kcc.Cfg.SocketPath)
   240  	defer func() {
   241  		if err != nil {
   242  			channel.Close()
   243  			channel = nil
   244  		}
   245  	}()
   246  	if err = kcc.startVM(); err != nil {
   247  		return
   248  	}
   249  	// TODO(kwalsh) reap and cleanup when vm dies; see linux_process_factory.go
   250  
   251  	// We need some way to wait for the socket to open before we can connect
   252  	// to it and return the ReadWriteCloser for communication.
   253  	tc := time.After(10 * time.Second)
   254  	glog.Info("Waiting for at most 10 seconds before returning channel")
   255  	<-tc
   256  
   257  	return
   258  }
   259  
   260  func (kcc *KvmCustomContainer) Cleanup() error {
   261  	// TODO(kwalsh) maybe also kill vm if still running?
   262  	return nil
   263  }