github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/go/tao/linux_docker_container_factory.go (about) 1 // Copyright (c) 2014, 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 "bytes" 19 "crypto/sha256" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "os/exec" 25 "os/signal" 26 "path" 27 "strings" 28 "syscall" 29 "time" 30 31 "github.com/golang/glog" 32 "github.com/jlmucb/cloudproxy/go/tao/auth" 33 "github.com/jlmucb/cloudproxy/go/util" 34 ) 35 36 // A DockerContainer represents a hosted program running as a Docker container. 37 // It uses os/exec.Cmd and the `docker` program to send commands to the Docker 38 // daemon rather than using the docker client API directly. This is so that this 39 // code doesn't depend on the docker code for now. 40 type DockerContainer struct { 41 42 // The spec from which this process was created. 43 spec HostedProgramSpec 44 45 // Hash of the docker image. 46 Hash []byte 47 48 // The factory responsible for the hosted process. 49 Factory *LinuxDockerContainerFactory 50 51 ImageName string 52 SocketPath string 53 CidfilePath string 54 RulesPath string 55 56 // The underlying docker process. 57 Cmd *exec.Cmd 58 59 // A channel to be signaled when the vm is done. 60 Done chan bool 61 } 62 63 // WaitChan returns a chan that will be signaled when the hosted vm is done. 64 func (dc *DockerContainer) WaitChan() <-chan bool { 65 return dc.Done 66 } 67 68 // Kill sends a SIGKILL signal to a docker container. 69 func (dc *DockerContainer) Kill() error { 70 cid, err := dc.ContainerName() 71 if err != nil { 72 return err 73 } 74 return docker(nil, "kill", cid) 75 } 76 77 func (dc *DockerContainer) ContainerName() (string, error) { 78 b, err := ioutil.ReadFile(dc.CidfilePath) 79 if err != nil { 80 return "", err 81 } 82 return strings.TrimSpace(string(b)), nil 83 } 84 85 func docker(stdin io.Reader, cmd string, args ...string) error { 86 c := exec.Command("docker", append([]string{cmd}, args...)...) 87 var b bytes.Buffer 88 c.Stdin = stdin 89 c.Stdout = &b 90 c.Stderr = &b 91 err := c.Run() 92 if err != nil { 93 glog.Errorf("Docker error %v: cmd=%v args=%v\n"+ 94 "begin docker output\n"+ 95 "%v\n"+ 96 "end docker output\n", err, cmd, args, b.String()) 97 } 98 return err 99 } 100 101 // StartDocker starts a docker container using the docker run subcommand. 102 func (dc *DockerContainer) StartDocker() error { 103 args := []string{"run", "--rm=true", "-v", dc.SocketPath + ":/tao"} 104 args = append(args, "--cidfile", dc.CidfilePath) 105 if dc.RulesPath != "" { 106 args = append(args, "-v", dc.RulesPath+":/"+path.Base(dc.RulesPath)) 107 } 108 // ContainerArgs has a name plus args passed directly to docker, i.e. before 109 // image name. Args are passed to the ENTRYPOINT within the Docker image, 110 // i.e. after image name. 111 // Note: Uid, Gid, Dir, and Env do not apply to docker hosted programs. 112 if len(dc.spec.ContainerArgs) > 1 { 113 args = append(args, dc.spec.ContainerArgs[1:]...) 114 } 115 args = append(args, dc.ImageName) 116 args = append(args, dc.spec.Args...) 117 dc.Cmd = exec.Command("docker", args...) 118 dc.Cmd.Stdin = dc.spec.Stdin 119 dc.Cmd.Stdout = dc.spec.Stdout 120 dc.Cmd.Stderr = dc.spec.Stderr 121 122 err := dc.Cmd.Start() 123 if err != nil { 124 return err 125 } 126 // Reap the child when the process dies. 127 go func() { 128 sc := make(chan os.Signal, 1) 129 signal.Notify(sc, syscall.SIGCHLD) 130 <-sc 131 dc.Cmd.Wait() 132 signal.Stop(sc) 133 134 time.Sleep(1 * time.Second) 135 docker(nil, "rmi", dc.ImageName) 136 dc.Done <- true 137 os.Remove(dc.CidfilePath) 138 close(dc.Done) // prevent any more blocking 139 }() 140 141 return nil 142 // TODO(kwalsh) put channel into p, remove the struct in linux_host.go 143 } 144 145 // Stop sends a SIGSTOP signal to a docker container. 146 func (dc *DockerContainer) Stop() error { 147 cid, err := dc.ContainerName() 148 if err != nil { 149 return err 150 } 151 return docker(nil, "kill", "-s", "STOP", cid) 152 } 153 154 // Pid returns a numeric ID for this docker container. 155 func (dc *DockerContainer) Pid() int { 156 return dc.Cmd.Process.Pid 157 } 158 159 // ExitStatus returns an exit code for the container. 160 func (dc *DockerContainer) ExitStatus() (int, error) { 161 s := dc.Cmd.ProcessState 162 if s == nil { 163 return -1, fmt.Errorf("Child has not exited") 164 } 165 if code, ok := (*s).Sys().(syscall.WaitStatus); ok { 166 return int(code), nil 167 } 168 return -1, fmt.Errorf("Couldn't get exit status\n") 169 } 170 171 // A LinuxDockerContainerFactory manages hosted programs started as docker 172 // containers over a given docker image. 173 type LinuxDockerContainerFactory struct { 174 SocketDir string 175 RulesPath string 176 } 177 178 // NewLinuxDockerContainerFactory returns a new HostedProgramFactory that can 179 // create docker containers to wrap programs. 180 func NewLinuxDockerContainerFactory(sockDir, rulesPath string) HostedProgramFactory { 181 return &LinuxDockerContainerFactory{ 182 SocketDir: sockDir, 183 RulesPath: rulesPath, 184 } 185 } 186 187 // NewHostedProgram initializes, but does not start, a hosted docker container. 188 func (ldcf *LinuxDockerContainerFactory) NewHostedProgram(spec HostedProgramSpec) (child HostedProgram, err error) { 189 190 // The imagename for the child is given by spec.ContainerArgs[0] 191 argv0 := "cloudproxy" 192 if len(spec.ContainerArgs) >= 1 { 193 argv0 = spec.ContainerArgs[0] 194 } 195 img := argv0 + ":" + getRandomFileName(nameLen) 196 197 inf, err := os.Open(spec.Path) 198 defer inf.Close() 199 if err != nil { 200 return 201 } 202 203 // Build the docker image, and hash the image as it is sent. 204 hasher := sha256.New() 205 err = docker(io.TeeReader(inf, hasher), "build", "-t", img, "-q", "-") 206 if err != nil { 207 return 208 } 209 210 hash := hasher.Sum(nil) 211 212 child = &DockerContainer{ 213 spec: spec, 214 ImageName: img, 215 Hash: hash, 216 Factory: ldcf, 217 Done: make(chan bool, 1), 218 } 219 220 return 221 } 222 223 // Spec returns the specification used to start the hosted docker container. 224 func (dc *DockerContainer) Spec() HostedProgramSpec { 225 return dc.spec 226 } 227 228 // Subprin returns the subprincipal representing the hosted docker container.. 229 func (dc *DockerContainer) Subprin() auth.SubPrin { 230 return FormatProcessSubprin(dc.spec.Id, dc.Hash) 231 } 232 233 // FormatDockerSubprin produces a string that represents a subprincipal with the 234 // given ID and hash. 235 func FormatDockerSubprin(id uint, hash []byte) auth.SubPrin { 236 var args []auth.Term 237 if id != 0 { 238 args = append(args, auth.Int(id)) 239 } 240 args = append(args, auth.Bytes(hash)) 241 return auth.SubPrin{auth.PrinExt{Name: "Container", Arg: args}} 242 } 243 244 // Start builds the docker container from the tar file and launches it. 245 func (dc *DockerContainer) Start() (channel io.ReadWriteCloser, err error) { 246 247 s := path.Join(dc.Factory.SocketDir, getRandomFileName(nameLen)) 248 dc.SocketPath = s + ".sock" 249 dc.CidfilePath = s + ".cid" 250 251 dc.RulesPath = dc.Factory.RulesPath 252 253 channel = util.NewUnixSingleReadWriteCloser(dc.SocketPath) 254 defer func() { 255 if err != nil { 256 channel.Close() 257 channel = nil 258 } 259 }() 260 261 args := []string{"run", "--rm=true", "-v", dc.SocketPath + ":/tao"} 262 args = append(args, "--cidfile", dc.CidfilePath) 263 if dc.RulesPath != "" { 264 args = append(args, "-v", dc.RulesPath+":/"+path.Base(dc.RulesPath)) 265 } 266 // ContainerArgs has a name plus args passed directly to docker, i.e. before 267 // image name. Args are passed to the ENTRYPOINT within the Docker image, 268 // i.e. after image name. 269 // Note: Uid, Gid, Dir, and Env do not apply to docker hosted programs. 270 if len(dc.spec.ContainerArgs) > 1 { 271 args = append(args, dc.spec.ContainerArgs[1:]...) 272 } 273 args = append(args, dc.ImageName) 274 args = append(args, dc.spec.Args...) 275 dc.Cmd = exec.Command("docker", args...) 276 dc.Cmd.Stdin = dc.spec.Stdin 277 dc.Cmd.Stdout = dc.spec.Stdout 278 dc.Cmd.Stderr = dc.spec.Stderr 279 280 err = dc.Cmd.Start() 281 if err != nil { 282 return 283 } 284 // Reap the child when the process dies. 285 go func() { 286 sc := make(chan os.Signal, 1) 287 signal.Notify(sc, syscall.SIGCHLD) 288 <-sc 289 dc.Cmd.Wait() 290 signal.Stop(sc) 291 292 time.Sleep(1 * time.Second) 293 docker(nil, "rmi", dc.ImageName) 294 dc.Done <- true 295 os.Remove(dc.CidfilePath) 296 close(dc.Done) // prevent any more blocking 297 }() 298 299 // TODO(kwalsh) put channel into dc, remove the struct in linux_host.go 300 return 301 } 302 303 func (p *DockerContainer) Cleanup() error { 304 // TODO(kwalsh) close channel, maybe also kill process if still running? 305 return nil 306 }