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 }