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  }