Images that are not whitelisted will be launched as normal containers. Only whitelisted ones will be launched as privileged. Additionally pull the image if it doesn't exist.
402 lines
11 KiB
Go
402 lines
11 KiB
Go
package docker
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
)
|
|
|
|
const (
|
|
Byte = 1
|
|
Kilobyte = 1024 * Byte
|
|
Megabyte = 1024 * Kilobyte
|
|
)
|
|
|
|
type DockerApi interface {
|
|
CreateNetwork(id string) error
|
|
ConnectNetwork(container, network, ip string) (string, error)
|
|
GetDaemonInfo() (types.Info, error)
|
|
GetSwarmPorts() ([]string, []uint16, error)
|
|
GetPorts() ([]uint16, error)
|
|
GetContainerStats(name string) (io.ReadCloser, error)
|
|
ContainerResize(name string, rows, cols uint) error
|
|
CreateAttachConnection(name string) (net.Conn, error)
|
|
CopyToContainer(containerName, destination, fileName string, content io.Reader) error
|
|
DeleteContainer(id string) error
|
|
CreateContainer(opts CreateContainerOpts) (string, error)
|
|
ExecAttach(instanceName string, command []string, out io.Writer) (int, error)
|
|
DisconnectNetwork(containerId, networkId string) error
|
|
DeleteNetwork(id string) error
|
|
Exec(instanceName string, command []string) (int, error)
|
|
}
|
|
|
|
type docker struct {
|
|
c *client.Client
|
|
}
|
|
|
|
func (d *docker) CreateNetwork(id string) error {
|
|
opts := types.NetworkCreate{Driver: "overlay", Attachable: true}
|
|
_, err := d.c.NetworkCreate(context.Background(), id, opts)
|
|
|
|
if err != nil {
|
|
log.Printf("Starting session err [%s]\n", err)
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *docker) ConnectNetwork(containerId, networkId, ip string) (string, error) {
|
|
settings := &network.EndpointSettings{}
|
|
if ip != "" {
|
|
settings.IPAddress = ip
|
|
}
|
|
err := d.c.NetworkConnect(context.Background(), networkId, containerId, settings)
|
|
|
|
if err != nil && !strings.Contains(err.Error(), "already exists") {
|
|
log.Printf("Connection container to network err [%s]\n", err)
|
|
|
|
return "", err
|
|
}
|
|
|
|
// Obtain the IP of the PWD container in this network
|
|
container, err := d.c.ContainerInspect(context.Background(), containerId)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
n, found := container.NetworkSettings.Networks[networkId]
|
|
if !found {
|
|
return "", fmt.Errorf("Container [%s] connected to the network [%s] but couldn't obtain it's IP address", containerId, networkId)
|
|
}
|
|
|
|
return n.IPAddress, nil
|
|
}
|
|
|
|
func (d *docker) GetDaemonInfo() (types.Info, error) {
|
|
return d.c.Info(context.Background())
|
|
}
|
|
|
|
func (d *docker) GetSwarmPorts() ([]string, []uint16, error) {
|
|
hosts := []string{}
|
|
ports := []uint16{}
|
|
|
|
nodesIdx := map[string]string{}
|
|
nodes, nodesErr := d.c.NodeList(context.Background(), types.NodeListOptions{})
|
|
if nodesErr != nil {
|
|
return nil, nil, nodesErr
|
|
}
|
|
for _, n := range nodes {
|
|
nodesIdx[n.ID] = n.Description.Hostname
|
|
hosts = append(hosts, n.Description.Hostname)
|
|
}
|
|
|
|
services, err := d.c.ServiceList(context.Background(), types.ServiceListOptions{})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
for _, service := range services {
|
|
for _, p := range service.Endpoint.Ports {
|
|
ports = append(ports, uint16(p.PublishedPort))
|
|
}
|
|
}
|
|
|
|
return hosts, ports, nil
|
|
}
|
|
|
|
func (d *docker) GetPorts() ([]uint16, error) {
|
|
opts := types.ContainerListOptions{}
|
|
containers, err := d.c.ContainerList(context.Background(), opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
openPorts := []uint16{}
|
|
for _, c := range containers {
|
|
for _, p := range c.Ports {
|
|
// When port is not published on the host docker return public port as 0, so we need to avoid it
|
|
if p.PublicPort != 0 {
|
|
openPorts = append(openPorts, p.PublicPort)
|
|
}
|
|
}
|
|
}
|
|
|
|
return openPorts, nil
|
|
}
|
|
|
|
func (d *docker) GetContainerStats(name string) (io.ReadCloser, error) {
|
|
stats, err := d.c.ContainerStats(context.Background(), name, false)
|
|
|
|
return stats.Body, err
|
|
}
|
|
|
|
func (d *docker) ContainerResize(name string, rows, cols uint) error {
|
|
return d.c.ContainerResize(context.Background(), name, types.ResizeOptions{Height: rows, Width: cols})
|
|
}
|
|
|
|
func (d *docker) CreateAttachConnection(name string) (net.Conn, error) {
|
|
ctx := context.Background()
|
|
|
|
conf := types.ContainerAttachOptions{true, true, true, true, "ctrl-^,ctrl-^", true}
|
|
conn, err := d.c.ContainerAttach(ctx, name, conf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return conn.Conn, nil
|
|
}
|
|
|
|
func (d *docker) CopyToContainer(containerName, destination, fileName string, content io.Reader) error {
|
|
r, w := io.Pipe()
|
|
b, readErr := ioutil.ReadAll(content)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
t := tar.NewWriter(w)
|
|
go func() {
|
|
t.WriteHeader(&tar.Header{Name: fileName, Mode: 0600, Size: int64(len(b))})
|
|
t.Write(b)
|
|
t.Close()
|
|
w.Close()
|
|
}()
|
|
return d.c.CopyToContainer(context.Background(), containerName, destination, r, types.CopyToContainerOptions{AllowOverwriteDirWithFile: true})
|
|
}
|
|
|
|
func (d *docker) DeleteContainer(id string) error {
|
|
return d.c.ContainerRemove(context.Background(), id, types.ContainerRemoveOptions{Force: true, RemoveVolumes: true})
|
|
}
|
|
|
|
type CreateContainerOpts struct {
|
|
Image string
|
|
SessionId string
|
|
PwdIpAddress string
|
|
ContainerName string
|
|
Hostname string
|
|
ServerCert []byte
|
|
ServerKey []byte
|
|
CACert []byte
|
|
Privileged bool
|
|
}
|
|
|
|
func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
|
|
// Make sure directories are available for the new instance container
|
|
containerDir := "/var/run/pwd"
|
|
containerCertDir := fmt.Sprintf("%s/certs", containerDir)
|
|
|
|
env := []string{}
|
|
|
|
// Write certs to container cert dir
|
|
if len(opts.ServerCert) > 0 {
|
|
env = append(env, `DOCKER_TLSCERT=\/var\/run\/pwd\/certs\/cert.pem`)
|
|
}
|
|
if len(opts.ServerKey) > 0 {
|
|
env = append(env, `DOCKER_TLSKEY=\/var\/run\/pwd\/certs\/key.pem`)
|
|
}
|
|
if len(opts.CACert) > 0 {
|
|
// if ca cert is specified, verify that clients that connects present a certificate signed by the CA
|
|
env = append(env, `DOCKER_TLSCACERT=\/var\/run\/pwd\/certs\/ca.pem`)
|
|
}
|
|
if len(opts.ServerCert) > 0 || len(opts.ServerKey) > 0 || len(opts.CACert) > 0 {
|
|
// if any of the certs is specified, enable TLS
|
|
env = append(env, "DOCKER_TLSENABLE=true")
|
|
} else {
|
|
env = append(env, "DOCKER_TLSENABLE=false")
|
|
}
|
|
|
|
h := &container.HostConfig{
|
|
NetworkMode: container.NetworkMode(opts.SessionId),
|
|
Privileged: opts.Privileged,
|
|
AutoRemove: true,
|
|
LogConfig: container.LogConfig{Config: map[string]string{"max-size": "10m", "max-file": "1"}},
|
|
}
|
|
|
|
if os.Getenv("APPARMOR_PROFILE") != "" {
|
|
h.SecurityOpt = []string{fmt.Sprintf("apparmor=%s", os.Getenv("APPARMOR_PROFILE"))}
|
|
}
|
|
|
|
var pidsLimit = int64(1000)
|
|
if envLimit := os.Getenv("MAX_PROCESSES"); envLimit != "" {
|
|
if i, err := strconv.Atoi(envLimit); err == nil {
|
|
pidsLimit = int64(i)
|
|
}
|
|
}
|
|
h.Resources.PidsLimit = pidsLimit
|
|
h.Resources.Memory = 4092 * Megabyte
|
|
t := true
|
|
h.Resources.OomKillDisable = &t
|
|
|
|
env = append(env, fmt.Sprintf("PWD_IP_ADDRESS=%s", opts.PwdIpAddress))
|
|
cf := &container.Config{Hostname: opts.Hostname,
|
|
Image: opts.Image,
|
|
Tty: true,
|
|
OpenStdin: true,
|
|
AttachStdin: true,
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Env: env,
|
|
}
|
|
networkConf := &network.NetworkingConfig{
|
|
map[string]*network.EndpointSettings{
|
|
opts.SessionId: &network.EndpointSettings{Aliases: []string{opts.Hostname}},
|
|
},
|
|
}
|
|
container, err := d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName)
|
|
|
|
if err != nil {
|
|
if client.IsErrImageNotFound(err) {
|
|
log.Printf("Unable to find image '%s' locally\n", opts.Image)
|
|
if err = d.pullImage(context.Background(), opts.Image); err != nil {
|
|
return "", err
|
|
}
|
|
container, err = d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
} else {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
if err := d.copyIfSet(opts.ServerCert, "cert.pem", containerCertDir, opts.ContainerName); err != nil {
|
|
return "", err
|
|
}
|
|
if err := d.copyIfSet(opts.ServerKey, "key.pem", containerCertDir, opts.ContainerName); err != nil {
|
|
return "", err
|
|
}
|
|
if err := d.copyIfSet(opts.CACert, "ca.pem", containerCertDir, opts.ContainerName); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
err = d.c.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cinfo, err := d.c.ContainerInspect(context.Background(), container.ID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return cinfo.NetworkSettings.Networks[opts.SessionId].IPAddress, nil
|
|
}
|
|
|
|
func (d *docker) pullImage(ctx context.Context, image string) error {
|
|
_, err := reference.ParseNormalizedNamed(image)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
options := types.ImageCreateOptions{}
|
|
|
|
responseBody, err := d.c.ImageCreate(ctx, image, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer responseBody.Close()
|
|
|
|
return jsonmessage.DisplayJSONMessagesStream(
|
|
responseBody,
|
|
os.Stderr,
|
|
os.Stdout.Fd(),
|
|
false,
|
|
nil)
|
|
}
|
|
|
|
func (d *docker) copyIfSet(content []byte, fileName, path, containerName string) error {
|
|
if len(content) > 0 {
|
|
return d.CopyToContainer(containerName, path, fileName, bytes.NewReader(content))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *docker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
|
|
e, err := d.c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command, AttachStdout: true, AttachStderr: true, Tty: true})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
resp, err := d.c.ContainerExecAttach(context.Background(), e.ID, types.ExecConfig{AttachStdout: true, AttachStderr: true, Tty: true})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
io.Copy(out, resp.Reader)
|
|
var ins types.ContainerExecInspect
|
|
for _ = range time.Tick(1 * time.Second) {
|
|
ins, err = d.c.ContainerExecInspect(context.Background(), e.ID)
|
|
if ins.Running {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
break
|
|
}
|
|
return ins.ExitCode, nil
|
|
|
|
}
|
|
|
|
func (d *docker) Exec(instanceName string, command []string) (int, error) {
|
|
e, err := d.c.ContainerExecCreate(context.Background(), instanceName, types.ExecConfig{Cmd: command})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = d.c.ContainerExecStart(context.Background(), e.ID, types.ExecStartCheck{})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var ins types.ContainerExecInspect
|
|
for _ = range time.Tick(1 * time.Second) {
|
|
ins, err = d.c.ContainerExecInspect(context.Background(), e.ID)
|
|
if ins.Running {
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
break
|
|
}
|
|
return ins.ExitCode, nil
|
|
}
|
|
|
|
func (d *docker) DisconnectNetwork(containerId, networkId string) error {
|
|
err := d.c.NetworkDisconnect(context.Background(), networkId, containerId, true)
|
|
|
|
if err != nil {
|
|
log.Printf("Disconnection of container from network err [%s]\n", err)
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *docker) DeleteNetwork(id string) error {
|
|
err := d.c.NetworkRemove(context.Background(), id)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewDocker(c *client.Client) *docker {
|
|
return &docker{c: c}
|
|
}
|