1

I would like to copy a zipped file from host machine to a container using go code running inside a container. The setup has go code running in a container with docker.sock mounted. The idea is to copy zip file from host machine to the container that runs go code. The path parameter is on the host machine. On host machine command line looks like this

docker cp hostFile.zip myContainer:/tmp/

The documentation for docker-client CopyToContainer looks

func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error

How to create content io.Reader argument ?

cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
    panic(err)
}

// TODO
// reader := io.Reader()
// reader := file.NewReader()
// tar.NewReader()

cli.CopyToContainer(context.Background(), containerID, dst, reader, types.CopyToContainerOptions{
    AllowOverwriteDirWithFile: true,
    CopyUIDGID:                true,
})
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
BhanuKiran
  • 2,631
  • 3
  • 20
  • 36
  • If you want to copy the file as is, then you can use `os.Open("/path/to/file.zip")`. – mkopriva Apr 05 '22 at 06:19
  • I am running this gocode inside a container.. The path actually doesn't exit inside the container. The path is on the host machine – BhanuKiran Apr 05 '22 at 06:20
  • 1
    If the code doesn't have access to the file then you can't copy it. – mkopriva Apr 05 '22 at 06:24
  • Since we are mounting the docker.sock on to the container, It is pretty muchthe same as running the command on host machine. – BhanuKiran Apr 05 '22 at 06:29
  • 3
    You should just create a volume and give the container access to the file from the host. The fact that you gave the container access to the socket means only that the container can communicate with the host's running docker engine. And if the docker engine doesn't provide an API for accessing files on the host then your container, without a volume, will, it seems to me, not have the ability to access host files either. – mkopriva Apr 05 '22 at 06:40

1 Answers1

2

There are a huge variety of things that implement io.Reader. In this case the normal way would be to open a file with os.Open, and then the resulting *os.File pointer is an io.Reader.

As you note in comments, though, this only helps you read and write things from your "local" filesystem. Having access to the host's Docker socket is super powerful but it doesn't directly give you read and write access to the host filesystem. (As @mkopriva suggests in a comment, launching your container with a docker run -v /host/path:/container/path bind mount is much simpler and avoids the massive security problem I'm about to discuss.)

What you need to do instead is launch a second container that bind-mounts the content you need, and read the file out of the container. It sounds like you're trying to write it into the local filesystem, which simplifies things. From a docker exec shell prompt inside the container you might do something like

docker run --rm -v /:/host busybox cat /host/some/path/hostFile.zip \
  > /tmp/hostFile.zip 

In Go it's more involved but still very doable (untested, imports omitted)

ctx := context.Background()
cid, err := client.ContainerCreate(
  ctx,
  &container.Config{
    Image: "docker.io/library/busybox:latest",
    Cmd: strslice.StrSlice{"cat", "/host/etc/shadow"},
  },
  &container.HostConfig{
    Mounts: []mount.Mount{
      {
        Type: mount.TypeBind,
        Source: "/",
        Target: "/host",
      },
    },
  },
  nil,
  nil,
  ""
)
if err != nil {
  return err
}

defer client.ContainerRemove(ctx, cid.ID, &types.ContainerRemoveOptions{})

rawLogs, err := client.ContainerLogs(
  ctx,
  cid.ID, 
  types.ContainerLogsOptions{ShowStdout: true},
)
if err != nil {
  return err
}
defer rawLogs.close()

go func() {
  of, err := os.Create("/tmp/host-shadow")
  if err != nil {
    panic(err)
  }
  defer of.Close()

  _ = stdcopy.StdCopy(of, io.Discard, rawLogs)
}()

done, cerr := client.ContainerWait(ctx, cid.ID, container. WaitConditionNotRunning)
for {
  select {
    case err := <-cerr:
      return err
    case waited := <-done:
      if waited.Error != nil {
        return errors.New(waited.Error.Message)
      } else if waited.StatusCode != 0 {
        return fmt.Errorf("cat container exited with status code %v", waited.StatusCode)
      } else {
        return nil
      }
  }
}

As I hinted earlier and showed in the code, this approach bypasses all controls on the host; I've decided to read back the host's /etc/shadow encrypted password file because I can, and nothing would stop me from writing it back with my choice of root password using basically the same approach. Owners, permissions, and anything else don't matter: the Docker daemon runs as root and most containers run as root by default (and you can explicitly request it if not).

David Maze
  • 130,717
  • 29
  • 175
  • 215
  • @mkopriva and David . Thanks for your replies. After going through your comments and some discussion with my colleagues, I have decided to mounted the folder instead of my initial thinking. – BhanuKiran Apr 06 '22 at 06:12