mirror of
https://github.com/containers/podman.git
synced 2025-05-20 08:36:23 +08:00
podman create/run need to load information from the image
We should be pulling information out of the image to set the defaults to use when setting up the container. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com> Closes: #110 Approved by: mheon
This commit is contained in:

committed by
Atomic Bot

parent
8aeb38e4a7
commit
00d38cb379
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"github.com/opencontainers/selinux/go-selinux/label"
|
"github.com/opencontainers/selinux/go-selinux/label"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -80,10 +81,11 @@ type createConfig struct {
|
|||||||
DNSServers []string //dns
|
DNSServers []string //dns
|
||||||
Entrypoint string //entrypoint
|
Entrypoint string //entrypoint
|
||||||
Env map[string]string //env
|
Env map[string]string //env
|
||||||
Expose []string //expose
|
ExposedPorts map[nat.Port]struct{}
|
||||||
GroupAdd []uint32 // group-add
|
GroupAdd []uint32 // group-add
|
||||||
Hostname string //hostname
|
Hostname string //hostname
|
||||||
Image string
|
Image string
|
||||||
|
ImageID string
|
||||||
Interactive bool //interactive
|
Interactive bool //interactive
|
||||||
IpcMode container.IpcMode //ipc
|
IpcMode container.IpcMode //ipc
|
||||||
IP6Address string //ipv6
|
IP6Address string //ipv6
|
||||||
@ -99,7 +101,8 @@ type createConfig struct {
|
|||||||
NetworkAlias []string //network-alias
|
NetworkAlias []string //network-alias
|
||||||
PidMode container.PidMode //pid
|
PidMode container.PidMode //pid
|
||||||
NsUser string
|
NsUser string
|
||||||
Pod string //pod
|
Pod string //pod
|
||||||
|
PortBindings nat.PortMap
|
||||||
Privileged bool //privileged
|
Privileged bool //privileged
|
||||||
Publish []string //publish
|
Publish []string //publish
|
||||||
PublishAll bool //publish-all
|
PublishAll bool //publish-all
|
||||||
@ -115,8 +118,7 @@ type createConfig struct {
|
|||||||
Sysctl map[string]string //sysctl
|
Sysctl map[string]string //sysctl
|
||||||
Tmpfs []string // tmpfs
|
Tmpfs []string // tmpfs
|
||||||
Tty bool //tty
|
Tty bool //tty
|
||||||
User uint32 //user
|
User string //user
|
||||||
Group uint32 // group
|
|
||||||
UtsMode container.UTSMode //uts
|
UtsMode container.UTSMode //uts
|
||||||
Volumes []string //volume
|
Volumes []string //volume
|
||||||
WorkDir string //workdir
|
WorkDir string //workdir
|
||||||
@ -148,7 +150,6 @@ var createCommand = cli.Command{
|
|||||||
func createCmd(c *cli.Context) error {
|
func createCmd(c *cli.Context) error {
|
||||||
// TODO should allow user to create based off a directory on the host not just image
|
// TODO should allow user to create based off a directory on the host not just image
|
||||||
// Need CLI support for this
|
// Need CLI support for this
|
||||||
var imageName string
|
|
||||||
if err := validateFlags(c, createFlags); err != nil {
|
if err := validateFlags(c, createFlags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -164,54 +165,19 @@ func createCmd(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deal with the image after all the args have been checked
|
|
||||||
createImage := runtime.NewImage(createConfig.Image)
|
|
||||||
createImage.LocalName, _ = createImage.GetLocalImageName()
|
|
||||||
if createImage.LocalName == "" {
|
|
||||||
// The image wasnt found by the user input'd name or its fqname
|
|
||||||
// Pull the image
|
|
||||||
var writer io.Writer
|
|
||||||
if !createConfig.Quiet {
|
|
||||||
writer = os.Stdout
|
|
||||||
}
|
|
||||||
createImage.Pull(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if createImage.LocalName != "" {
|
|
||||||
nameIsID, err := runtime.IsImageID(createImage.LocalName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nameIsID {
|
|
||||||
// If the input from the user is an ID, then we need to get the image
|
|
||||||
// name for cstorage
|
|
||||||
createImage.LocalName, err = createImage.GetNameByID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageName = createImage.LocalName
|
|
||||||
} else {
|
|
||||||
imageName, err = createImage.GetFQName()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
imageID, err := createImage.GetImageID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
options, err := createConfig.GetContainerCreateOptions()
|
options, err := createConfig.GetContainerCreateOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "unable to parse new container options")
|
return errors.Wrapf(err, "unable to parse new container options")
|
||||||
}
|
}
|
||||||
// Gather up the options for NewContainer which consist of With... funcs
|
// Gather up the options for NewContainer which consist of With... funcs
|
||||||
options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
|
options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
|
||||||
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
||||||
|
options = append(options, libpod.WithLabels(createConfig.Labels))
|
||||||
|
options = append(options, libpod.WithUser(createConfig.User))
|
||||||
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
|
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
|
||||||
ctr, err := runtime.NewContainer(runtimeSpec, options...)
|
ctr, err := runtime.NewContainer(runtimeSpec, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -300,13 +266,101 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
|
||||||
|
// TODO Handle exposed ports from image
|
||||||
|
// Currently ignoring imageExposedPorts
|
||||||
|
|
||||||
|
ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range c.StringSlice("expose") {
|
||||||
|
// Merge in exposed ports to the map of published ports
|
||||||
|
if strings.Contains(e, ":") {
|
||||||
|
return nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
|
||||||
|
}
|
||||||
|
//support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
|
||||||
|
proto, port := nat.SplitProtoPort(e)
|
||||||
|
//parse the start and end port and create a sequence of ports to expose
|
||||||
|
//if expose a port, the start and end port are the same
|
||||||
|
start, end, err := nat.ParsePortRange(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", e, err)
|
||||||
|
}
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if _, exists := ports[p]; !exists {
|
||||||
|
ports[p] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ports, portBindings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageData pulls down the image if not stored locally and extracts the
|
||||||
|
// default container runtime data out of it. imageData returns the data
|
||||||
|
// to the caller. Example Data: Entrypoint, Env, WorkingDir, Labels ...
|
||||||
|
func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, string, *libpod.ImageData, error) {
|
||||||
|
var err error
|
||||||
|
// Deal with the image after all the args have been checked
|
||||||
|
createImage := runtime.NewImage(image)
|
||||||
|
createImage.LocalName, _ = createImage.GetLocalImageName()
|
||||||
|
if createImage.LocalName == "" {
|
||||||
|
// The image wasnt found by the user input'd name or its fqname
|
||||||
|
// Pull the image
|
||||||
|
var writer io.Writer
|
||||||
|
if !c.Bool("quiet") {
|
||||||
|
writer = os.Stdout
|
||||||
|
}
|
||||||
|
createImage.Pull(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageName string
|
||||||
|
if createImage.LocalName != "" {
|
||||||
|
nameIsID, err := runtime.IsImageID(createImage.LocalName)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
if nameIsID {
|
||||||
|
// If the input from the user is an ID, then we need to get the image
|
||||||
|
// name for cstorage
|
||||||
|
createImage.LocalName, err = createImage.GetNameByID()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageName = createImage.LocalName
|
||||||
|
} else {
|
||||||
|
imageName, err = createImage.GetFQName()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
imageID, err := createImage.GetImageID()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, err
|
||||||
|
}
|
||||||
|
storageImage, err := runtime.GetImage(image)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, errors.Wrapf(err, "error getting storage image %q", image)
|
||||||
|
}
|
||||||
|
data, err := runtime.GetImageInspectInfo(*storageImage)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", nil, errors.Wrapf(err, "error parsing image data %q", image)
|
||||||
|
}
|
||||||
|
return imageName, imageID, data, err
|
||||||
|
}
|
||||||
|
|
||||||
// Parses CLI options related to container creation into a config which can be
|
// Parses CLI options related to container creation into a config which can be
|
||||||
// parsed into an OCI runtime spec
|
// parsed into an OCI runtime spec
|
||||||
func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) {
|
func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) {
|
||||||
var command []string
|
var command []string
|
||||||
var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
|
var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
|
||||||
var blkioWeight uint16
|
var blkioWeight uint16
|
||||||
var uid, gid uint32
|
|
||||||
|
|
||||||
if len(c.Args()) < 1 {
|
if len(c.Args()) < 1 {
|
||||||
return nil, errors.Errorf("image name or ID is required")
|
return nil, errors.Errorf("image name or ID is required")
|
||||||
@ -317,33 +371,14 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
|
|||||||
command = c.Args()[1:]
|
command = c.Args()[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// LABEL VARIABLES
|
|
||||||
labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels"))
|
|
||||||
if err != nil {
|
|
||||||
return &createConfig{}, errors.Wrapf(err, "unable to process labels")
|
|
||||||
}
|
|
||||||
// ENVIRONMENT VARIABLES
|
|
||||||
env := defaultEnvVariables
|
|
||||||
if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil {
|
|
||||||
return &createConfig{}, errors.Wrapf(err, "unable to process environment variables")
|
|
||||||
}
|
|
||||||
|
|
||||||
sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=")
|
sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &createConfig{}, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
|
return nil, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
|
||||||
}
|
}
|
||||||
|
|
||||||
groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add"))
|
groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &createConfig{}, errors.Wrapf(err, "invalid value for groups provided")
|
return nil, errors.Wrapf(err, "invalid value for groups provided")
|
||||||
}
|
|
||||||
|
|
||||||
if c.String("user") != "" {
|
|
||||||
// TODO
|
|
||||||
// We need to mount the imagefs and get the uid/gid
|
|
||||||
// For now, user zeros
|
|
||||||
uid = 0
|
|
||||||
gid = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.String("memory") != "" {
|
if c.String("memory") != "" {
|
||||||
@ -417,14 +452,79 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
|
|||||||
}
|
}
|
||||||
shmDir = ctr.ShmDir()
|
shmDir = ctr.ShmDir()
|
||||||
}
|
}
|
||||||
stopSignal := syscall.SIGTERM
|
|
||||||
|
imageName, imageID, data, err := imageData(c, runtime, image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// USER
|
||||||
|
user := c.String("user")
|
||||||
|
if user == "" {
|
||||||
|
user = data.Config.User
|
||||||
|
}
|
||||||
|
|
||||||
|
// STOP SIGNAL
|
||||||
|
stopSignal := syscall.SIGINT
|
||||||
|
signalString := data.Config.StopSignal
|
||||||
if c.IsSet("stop-signal") {
|
if c.IsSet("stop-signal") {
|
||||||
stopSignal, err = signal.ParseSignal(c.String("stop-signal"))
|
signalString = c.String("stop-signal")
|
||||||
|
}
|
||||||
|
if signalString != "" {
|
||||||
|
stopSignal, err = signal.ParseSignal(signalString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ENVIRONMENT VARIABLES
|
||||||
|
env := defaultEnvVariables
|
||||||
|
for _, e := range data.Config.Env {
|
||||||
|
split := strings.SplitN(e, "=", 2)
|
||||||
|
if len(split) > 1 {
|
||||||
|
env[split[0]] = split[1]
|
||||||
|
} else {
|
||||||
|
env[split[0]] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "unable to process environment variables")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LABEL VARIABLES
|
||||||
|
labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "unable to process labels")
|
||||||
|
}
|
||||||
|
for key, val := range data.Config.Labels {
|
||||||
|
if _, ok := labels[key]; !ok {
|
||||||
|
labels[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WORKING DIRECTORY
|
||||||
|
workDir := c.String("workdir")
|
||||||
|
if workDir == "" {
|
||||||
|
workDir = data.Config.WorkingDir
|
||||||
|
}
|
||||||
|
|
||||||
|
// COMMAND
|
||||||
|
if len(command) == 0 {
|
||||||
|
command = data.Config.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENTRYPOINT
|
||||||
|
entrypoint := c.String("entrypoint")
|
||||||
|
if entrypoint == "" {
|
||||||
|
entrypoint = strings.Join(data.Config.Entrypoint, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPOSED PORTS
|
||||||
|
ports, portBindings, err := exposedPorts(c, data.Config.ExposedPorts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
config := &createConfig{
|
config := &createConfig{
|
||||||
Runtime: runtime,
|
Runtime: runtime,
|
||||||
CapAdd: c.StringSlice("cap-add"),
|
CapAdd: c.StringSlice("cap-add"),
|
||||||
@ -436,12 +536,13 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
|
|||||||
DNSOpt: c.StringSlice("dns-opt"),
|
DNSOpt: c.StringSlice("dns-opt"),
|
||||||
DNSSearch: c.StringSlice("dns-search"),
|
DNSSearch: c.StringSlice("dns-search"),
|
||||||
DNSServers: c.StringSlice("dns"),
|
DNSServers: c.StringSlice("dns"),
|
||||||
Entrypoint: c.String("entrypoint"),
|
Entrypoint: entrypoint,
|
||||||
Env: env,
|
Env: env,
|
||||||
Expose: c.StringSlice("expose"),
|
ExposedPorts: ports,
|
||||||
GroupAdd: groupAdd,
|
GroupAdd: groupAdd,
|
||||||
Hostname: c.String("hostname"),
|
Hostname: c.String("hostname"),
|
||||||
Image: image,
|
Image: imageName,
|
||||||
|
ImageID: imageID,
|
||||||
Interactive: c.Bool("interactive"),
|
Interactive: c.Bool("interactive"),
|
||||||
IP6Address: c.String("ipv6"),
|
IP6Address: c.String("ipv6"),
|
||||||
IPAddress: c.String("ip"),
|
IPAddress: c.String("ip"),
|
||||||
@ -461,6 +562,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
|
|||||||
Privileged: c.Bool("privileged"),
|
Privileged: c.Bool("privileged"),
|
||||||
Publish: c.StringSlice("publish"),
|
Publish: c.StringSlice("publish"),
|
||||||
PublishAll: c.Bool("publish-all"),
|
PublishAll: c.Bool("publish-all"),
|
||||||
|
PortBindings: portBindings,
|
||||||
Quiet: c.Bool("quiet"),
|
Quiet: c.Bool("quiet"),
|
||||||
ReadOnlyRootfs: c.Bool("read-only"),
|
ReadOnlyRootfs: c.Bool("read-only"),
|
||||||
Resources: createResourceConfig{
|
Resources: createResourceConfig{
|
||||||
@ -499,10 +601,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er
|
|||||||
Sysctl: sysctl,
|
Sysctl: sysctl,
|
||||||
Tmpfs: c.StringSlice("tmpfs"),
|
Tmpfs: c.StringSlice("tmpfs"),
|
||||||
Tty: tty,
|
Tty: tty,
|
||||||
User: uid,
|
User: user,
|
||||||
Group: gid,
|
|
||||||
Volumes: c.StringSlice("volume"),
|
Volumes: c.StringSlice("volume"),
|
||||||
WorkDir: c.String("workdir"),
|
WorkDir: workDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.Privileged {
|
if !config.Privileged {
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/docker/go-connections/nat"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectatomic/libpod/cmd/podman/formats"
|
"github.com/projectatomic/libpod/cmd/podman/formats"
|
||||||
@ -287,7 +288,7 @@ type HostConfig struct {
|
|||||||
ContainerIDFile string `json:"ContainerIDFile"`
|
ContainerIDFile string `json:"ContainerIDFile"`
|
||||||
LogConfig *LogConfig `json:"LogConfig"` //TODO
|
LogConfig *LogConfig `json:"LogConfig"` //TODO
|
||||||
NetworkMode string `json:"NetworkMode"`
|
NetworkMode string `json:"NetworkMode"`
|
||||||
PortBindings map[string]struct{} `json:"PortBindings"` //TODO
|
PortBindings nat.PortMap `json:"PortBindings"` //TODO
|
||||||
AutoRemove bool `json:"AutoRemove"`
|
AutoRemove bool `json:"AutoRemove"`
|
||||||
CapAdd []string `json:"CapAdd"`
|
CapAdd []string `json:"CapAdd"`
|
||||||
CapDrop []string `json:"CapDrop"`
|
CapDrop []string `json:"CapDrop"`
|
||||||
|
@ -11,14 +11,12 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
)
|
)
|
||||||
@ -713,77 +711,6 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolin
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseUser parses the the uid and gid in the format <name|uid>[:<group|gid>]
|
|
||||||
// for user flag
|
|
||||||
// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66
|
|
||||||
func parseUser(rootdir, userspec string) (specs.User, error) { //nolint
|
|
||||||
var gid64 uint64
|
|
||||||
var gerr error = user.UnknownGroupError("error looking up group")
|
|
||||||
|
|
||||||
spec := strings.SplitN(userspec, ":", 2)
|
|
||||||
userspec = spec[0]
|
|
||||||
groupspec := ""
|
|
||||||
if userspec == "" {
|
|
||||||
return specs.User{}, nil
|
|
||||||
}
|
|
||||||
if len(spec) > 1 {
|
|
||||||
groupspec = spec[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
uid64, uerr := strconv.ParseUint(userspec, 10, 32)
|
|
||||||
if uerr == nil && groupspec == "" {
|
|
||||||
// We parsed the user name as a number, and there's no group
|
|
||||||
// component, so we need to look up the user's primary GID.
|
|
||||||
var name string
|
|
||||||
name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
|
|
||||||
if gerr == nil {
|
|
||||||
userspec = name
|
|
||||||
} else {
|
|
||||||
if userrec, err := user.LookupId(userspec); err == nil {
|
|
||||||
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
|
|
||||||
userspec = userrec.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if uerr != nil {
|
|
||||||
uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
|
|
||||||
gerr = uerr
|
|
||||||
}
|
|
||||||
if uerr != nil {
|
|
||||||
if userrec, err := user.Lookup(userspec); err == nil {
|
|
||||||
uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
|
|
||||||
gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if groupspec != "" {
|
|
||||||
gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
|
|
||||||
if gerr != nil {
|
|
||||||
gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
|
|
||||||
}
|
|
||||||
if gerr != nil {
|
|
||||||
if group, err := user.LookupGroup(groupspec); err == nil {
|
|
||||||
gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if uerr == nil && gerr == nil {
|
|
||||||
u := specs.User{
|
|
||||||
UID: uint32(uid64),
|
|
||||||
GID: uint32(gid64),
|
|
||||||
Username: userspec,
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := errors.Wrapf(uerr, "error determining run uid")
|
|
||||||
if uerr == nil {
|
|
||||||
err = errors.Wrapf(gerr, "error determining run gid")
|
|
||||||
}
|
|
||||||
return specs.User{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
|
||||||
func convertKVStringsToMap(values []string) map[string]string {
|
func convertKVStringsToMap(values []string) map[string]string {
|
||||||
result := make(map[string]string, len(values))
|
result := make(map[string]string, len(values))
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -26,7 +25,6 @@ var runCommand = cli.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runCmd(c *cli.Context) error {
|
func runCmd(c *cli.Context) error {
|
||||||
var imageName string
|
|
||||||
if err := validateFlags(c, createFlags); err != nil {
|
if err := validateFlags(c, createFlags); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -41,51 +39,10 @@ func runCmd(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
createImage := runtime.NewImage(createConfig.Image)
|
|
||||||
createImage.LocalName, _ = createImage.GetLocalImageName()
|
|
||||||
if createImage.LocalName == "" {
|
|
||||||
// The image wasnt found by the user input'd name or its fqname
|
|
||||||
// Pull the image
|
|
||||||
var writer io.Writer
|
|
||||||
if !createConfig.Quiet {
|
|
||||||
writer = os.Stdout
|
|
||||||
}
|
|
||||||
createImage.Pull(writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
runtimeSpec, err := createConfigToOCISpec(createConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logrus.Debug("spec is ", runtimeSpec)
|
|
||||||
|
|
||||||
if createImage.LocalName != "" {
|
|
||||||
nameIsID, err := runtime.IsImageID(createImage.LocalName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nameIsID {
|
|
||||||
// If the input from the user is an ID, then we need to get the image
|
|
||||||
// name for cstorage
|
|
||||||
createImage.LocalName, err = createImage.GetNameByID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imageName = createImage.LocalName
|
|
||||||
} else {
|
|
||||||
imageName, err = createImage.GetFQName()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logrus.Debug("imageName is ", imageName)
|
|
||||||
|
|
||||||
imageID, err := createImage.GetImageID()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logrus.Debug("imageID is ", imageID)
|
|
||||||
|
|
||||||
options, err := createConfig.GetContainerCreateOptions()
|
options, err := createConfig.GetContainerCreateOptions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,8 +50,10 @@ func runCmd(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gather up the options for NewContainer which consist of With... funcs
|
// Gather up the options for NewContainer which consist of With... funcs
|
||||||
options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
|
options = append(options, libpod.WithRootFSFromImage(createConfig.ImageID, createConfig.Image, true))
|
||||||
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel))
|
||||||
|
options = append(options, libpod.WithLabels(createConfig.Labels))
|
||||||
|
options = append(options, libpod.WithUser(createConfig.User))
|
||||||
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
|
options = append(options, libpod.WithShmDir(createConfig.ShmDir))
|
||||||
ctr, err := runtime.NewContainer(runtimeSpec, options...)
|
ctr, err := runtime.NewContainer(runtimeSpec, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,6 +66,16 @@ func runCmd(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
logrus.Debugf("container storage created for %q", ctr.ID())
|
logrus.Debugf("container storage created for %q", ctr.ID())
|
||||||
|
|
||||||
|
createConfigJSON, err := json.Marshal(createConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debug("new container created ", ctr.ID())
|
||||||
|
|
||||||
if c.String("cidfile") != "" {
|
if c.String("cidfile") != "" {
|
||||||
libpod.WriteFile(ctr.ID(), c.String("cidfile"))
|
libpod.WriteFile(ctr.ID(), c.String("cidfile"))
|
||||||
return nil
|
return nil
|
||||||
|
@ -193,9 +193,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
|
|||||||
g.SetProcessCwd(config.WorkDir)
|
g.SetProcessCwd(config.WorkDir)
|
||||||
g.SetProcessArgs(config.Command)
|
g.SetProcessArgs(config.Command)
|
||||||
g.SetProcessTerminal(config.Tty)
|
g.SetProcessTerminal(config.Tty)
|
||||||
// User and Group must go together
|
|
||||||
g.SetProcessUID(config.User)
|
|
||||||
g.SetProcessGID(config.Group)
|
|
||||||
for _, gid := range config.GroupAdd {
|
for _, gid := range config.GroupAdd {
|
||||||
g.AddProcessAdditionalGid(gid)
|
g.AddProcessAdditionalGid(gid)
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/projectatomic/libpod/libpod/driver"
|
"github.com/projectatomic/libpod/libpod/driver"
|
||||||
crioAnnotations "github.com/projectatomic/libpod/pkg/annotations"
|
crioAnnotations "github.com/projectatomic/libpod/pkg/annotations"
|
||||||
|
"github.com/projectatomic/libpod/pkg/chrootuser"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/ulule/deepcopier"
|
"github.com/ulule/deepcopier"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -153,7 +154,8 @@ type ContainerConfig struct {
|
|||||||
SharedNamespaceMap map[string]string `json:"sharedNamespaces"`
|
SharedNamespaceMap map[string]string `json:"sharedNamespaces"`
|
||||||
// Time container was created
|
// Time container was created
|
||||||
CreatedTime time.Time `json:"createdTime"`
|
CreatedTime time.Time `json:"createdTime"`
|
||||||
|
// User/GID to use within the container
|
||||||
|
User string `json:"user"`
|
||||||
// TODO save log location here and pass into OCI code
|
// TODO save log location here and pass into OCI code
|
||||||
// TODO allow overriding of log path
|
// TODO allow overriding of log path
|
||||||
}
|
}
|
||||||
@ -440,7 +442,6 @@ func newContainer(rspec *spec.Spec, lockDir string) (*Container, error) {
|
|||||||
|
|
||||||
ctr.config.Spec = new(spec.Spec)
|
ctr.config.Spec = new(spec.Spec)
|
||||||
deepcopier.Copy(rspec).To(ctr.config.Spec)
|
deepcopier.Copy(rspec).To(ctr.config.Spec)
|
||||||
|
|
||||||
ctr.config.CreatedTime = time.Now()
|
ctr.config.CreatedTime = time.Now()
|
||||||
|
|
||||||
// Path our lock file will reside at
|
// Path our lock file will reside at
|
||||||
@ -614,6 +615,20 @@ func (c *Container) Init() (err error) {
|
|||||||
g.AddBindMount(runDirResolv, "/etc/resolv.conf", []string{"rw"})
|
g.AddBindMount(runDirResolv, "/etc/resolv.conf", []string{"rw"})
|
||||||
// Bind mount hosts
|
// Bind mount hosts
|
||||||
g.AddBindMount(runDirHosts, "/etc/hosts", []string{"rw"})
|
g.AddBindMount(runDirHosts, "/etc/hosts", []string{"rw"})
|
||||||
|
|
||||||
|
if c.config.User != "" {
|
||||||
|
if !c.state.Mounted {
|
||||||
|
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate User field", c.ID())
|
||||||
|
}
|
||||||
|
uid, gid, err := chrootuser.GetUser(c.state.Mountpoint, c.config.User)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// User and Group must go together
|
||||||
|
g.SetProcessUID(uid)
|
||||||
|
g.SetProcessGID(gid)
|
||||||
|
}
|
||||||
|
|
||||||
c.runningSpec = g.Spec()
|
c.runningSpec = g.Spec()
|
||||||
c.runningSpec.Root.Path = c.state.Mountpoint
|
c.runningSpec.Root.Path = c.state.Mountpoint
|
||||||
c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano)
|
c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano)
|
||||||
@ -1078,7 +1093,7 @@ func (c *Container) mountStorage() (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mountPoint, err := c.runtime.storageService.StartContainer(c.ID())
|
mountPoint, err := c.runtime.storageService.MountContainerImage(c.ID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error mounting storage for container %s", c.ID())
|
return errors.Wrapf(err, "error mounting storage for container %s", c.ID())
|
||||||
}
|
}
|
||||||
@ -1124,7 +1139,7 @@ func (c *Container) cleanupStorage() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Also unmount storage
|
// Also unmount storage
|
||||||
if err := c.runtime.storageService.StopContainer(c.ID()); err != nil {
|
if err := c.runtime.storageService.UnmountContainerImage(c.ID()); err != nil {
|
||||||
return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID())
|
return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +307,19 @@ func WithSELinuxLabels(processLabel, mountLabel string) CtrCreateOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithUser sets the user identity field in configutation
|
||||||
|
// Valid uses [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||||
|
func WithUser(user string) CtrCreateOption {
|
||||||
|
return func(ctr *Container) error {
|
||||||
|
if ctr.valid {
|
||||||
|
return ErrCtrFinalized
|
||||||
|
}
|
||||||
|
|
||||||
|
ctr.config.User = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithRootFSFromImage sets up a fresh root filesystem using the given image
|
// WithRootFSFromImage sets up a fresh root filesystem using the given image
|
||||||
// If useImageConfig is specified, image volumes, environment variables, and
|
// If useImageConfig is specified, image volumes, environment variables, and
|
||||||
// other configuration from the image will be added to the config
|
// other configuration from the image will be added to the config
|
||||||
|
@ -25,14 +25,14 @@ type CtrCreateOption func(*Container) error
|
|||||||
type ContainerFilter func(*Container) bool
|
type ContainerFilter func(*Container) bool
|
||||||
|
|
||||||
// NewContainer creates a new container from a given OCI config
|
// NewContainer creates a new container from a given OCI config
|
||||||
func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) {
|
func (r *Runtime) NewContainer(rSpec *spec.Spec, options ...CtrCreateOption) (c *Container, err error) {
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
if !r.valid {
|
if !r.valid {
|
||||||
return nil, ErrRuntimeStopped
|
return nil, ErrRuntimeStopped
|
||||||
}
|
}
|
||||||
|
|
||||||
ctr, err := newContainer(spec, r.lockDir)
|
ctr, err := newContainer(rSpec, r.lockDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ func (r *storageService) GetContainerMetadata(idOrName string) (RuntimeContainer
|
|||||||
return metadata, nil
|
return metadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *storageService) StartContainer(idOrName string) (string, error) {
|
func (r *storageService) MountContainerImage(idOrName string) (string, error) {
|
||||||
container, err := r.store.Container(idOrName)
|
container, err := r.store.Container(idOrName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == storage.ErrContainerUnknown {
|
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||||
@ -221,7 +221,7 @@ func (r *storageService) StartContainer(idOrName string) (string, error) {
|
|||||||
return mountPoint, nil
|
return mountPoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *storageService) StopContainer(idOrName string) error {
|
func (r *storageService) UnmountContainerImage(idOrName string) error {
|
||||||
if idOrName == "" {
|
if idOrName == "" {
|
||||||
return ErrEmptyID
|
return ErrEmptyID
|
||||||
}
|
}
|
||||||
|
71
pkg/chrootuser/user.go
Normal file
71
pkg/chrootuser/user.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package chrootuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetUser will return the uid, gid of the user specified in the userspec
|
||||||
|
// it will use the /etc/password and /etc/shadow files inside of the rootdir
|
||||||
|
// to return this information.
|
||||||
|
// userspace format [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||||
|
func GetUser(rootdir, userspec string) (uint32, uint32, error) {
|
||||||
|
var gid64 uint64
|
||||||
|
var gerr error = user.UnknownGroupError("error looking up group")
|
||||||
|
|
||||||
|
spec := strings.SplitN(userspec, ":", 2)
|
||||||
|
userspec = spec[0]
|
||||||
|
groupspec := ""
|
||||||
|
if userspec == "" {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
if len(spec) > 1 {
|
||||||
|
groupspec = spec[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
uid64, uerr := strconv.ParseUint(userspec, 10, 32)
|
||||||
|
if uerr == nil && groupspec == "" {
|
||||||
|
// We parsed the user name as a number, and there's no group
|
||||||
|
// component, so try to look up the primary GID of the user who
|
||||||
|
// has this UID.
|
||||||
|
var name string
|
||||||
|
name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
|
||||||
|
if gerr == nil {
|
||||||
|
userspec = name
|
||||||
|
} else {
|
||||||
|
// Leave userspec alone, but swallow the error and just
|
||||||
|
// use GID 0.
|
||||||
|
gid64 = 0
|
||||||
|
gerr = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uerr != nil {
|
||||||
|
// The user ID couldn't be parsed as a number, so try to look
|
||||||
|
// up the user's UID and primary GID.
|
||||||
|
uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
|
||||||
|
gerr = uerr
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupspec != "" {
|
||||||
|
// We have a group name or number, so parse it.
|
||||||
|
gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
|
||||||
|
if gerr != nil {
|
||||||
|
// The group couldn't be parsed as a number, so look up
|
||||||
|
// the group's GID.
|
||||||
|
gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if uerr == nil && gerr == nil {
|
||||||
|
return uint32(uid64), uint32(gid64), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := errors.Wrapf(uerr, "error determining run uid")
|
||||||
|
if uerr == nil {
|
||||||
|
err = errors.Wrapf(gerr, "error determining run gid")
|
||||||
|
}
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
19
pkg/chrootuser/user_basic.go
Normal file
19
pkg/chrootuser/user_basic.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package chrootuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
|
||||||
|
return 0, 0, errors.New("user lookup not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
|
||||||
|
return 0, errors.New("group lookup not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
|
||||||
|
return "", 0, errors.New("primary group lookup by uid not supported")
|
||||||
|
}
|
235
pkg/chrootuser/user_linux.go
Normal file
235
pkg/chrootuser/user_linux.go
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package chrootuser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/user"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containers/storage/pkg/reexec"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
openChrootedCommand = "chrootuser-open"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reexec.Register(openChrootedCommand, openChrootedFileMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openChrootedFileMain() {
|
||||||
|
status := 0
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// Our first parameter is the directory to chroot into.
|
||||||
|
if err := unix.Chdir(flag.Arg(0)); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "chdir(): %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := unix.Chroot(flag.Arg(0)); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "chroot(): %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
// Anything else is a file we want to dump out.
|
||||||
|
for _, filename := range flag.Args()[1:] {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "open(%q): %v", filename, err)
|
||||||
|
status = 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err = io.Copy(os.Stdout, f)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "read(%q): %v", filename, err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
os.Exit(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func openChrootedFile(rootdir, filename string) (*exec.Cmd, io.ReadCloser, error) {
|
||||||
|
// The child process expects a chroot and one or more filenames that
|
||||||
|
// will be consulted relative to the chroot directory and concatenated
|
||||||
|
// to its stdout. Start it up.
|
||||||
|
cmd := reexec.Command(openChrootedCommand, rootdir, filename)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
// Hand back the child's stdout for reading, and the child to reap.
|
||||||
|
return cmd, stdout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
lookupUser, lookupGroup sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type lookupPasswdEntry struct {
|
||||||
|
name string
|
||||||
|
uid uint64
|
||||||
|
gid uint64
|
||||||
|
}
|
||||||
|
type lookupGroupEntry struct {
|
||||||
|
name string
|
||||||
|
gid uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func readWholeLine(rc *bufio.Reader) ([]byte, error) {
|
||||||
|
line, isPrefix, err := rc.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for isPrefix {
|
||||||
|
// We didn't get a whole line. Keep reading chunks until we find an end of line, and discard them.
|
||||||
|
for isPrefix {
|
||||||
|
logrus.Debugf("discarding partial line %q", string(line))
|
||||||
|
_, isPrefix, err = rc.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// That last read was the end of a line, so now we try to read the (beginning of?) the next line.
|
||||||
|
line, isPrefix, err = rc.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return line, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNextPasswd(rc *bufio.Reader) *lookupPasswdEntry {
|
||||||
|
line, err := readWholeLine(rc)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fields := strings.Split(string(line), ":")
|
||||||
|
if len(fields) < 7 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
uid, err := strconv.ParseUint(fields[2], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gid, err := strconv.ParseUint(fields[3], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &lookupPasswdEntry{
|
||||||
|
name: fields[0],
|
||||||
|
uid: uid,
|
||||||
|
gid: gid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNextGroup(rc *bufio.Reader) *lookupGroupEntry {
|
||||||
|
line, err := readWholeLine(rc)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fields := strings.Split(string(line), ":")
|
||||||
|
if len(fields) < 4 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gid, err := strconv.ParseUint(fields[2], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &lookupGroupEntry{
|
||||||
|
name: fields[0],
|
||||||
|
gid: gid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupUserInContainer(rootdir, username string) (uid uint64, gid uint64, err error) {
|
||||||
|
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}()
|
||||||
|
rc := bufio.NewReader(f)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
lookupUser.Lock()
|
||||||
|
defer lookupUser.Unlock()
|
||||||
|
|
||||||
|
pwd := parseNextPasswd(rc)
|
||||||
|
for pwd != nil {
|
||||||
|
if pwd.name != username {
|
||||||
|
pwd = parseNextPasswd(rc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return pwd.uid, pwd.gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroupForUIDInContainer(rootdir string, userid uint64) (username string, gid uint64, err error) {
|
||||||
|
cmd, f, err := openChrootedFile(rootdir, "/etc/passwd")
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}()
|
||||||
|
rc := bufio.NewReader(f)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
lookupUser.Lock()
|
||||||
|
defer lookupUser.Unlock()
|
||||||
|
|
||||||
|
pwd := parseNextPasswd(rc)
|
||||||
|
for pwd != nil {
|
||||||
|
if pwd.uid != userid {
|
||||||
|
pwd = parseNextPasswd(rc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return pwd.name, pwd.gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroupInContainer(rootdir, groupname string) (gid uint64, err error) {
|
||||||
|
cmd, f, err := openChrootedFile(rootdir, "/etc/group")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = cmd.Wait()
|
||||||
|
}()
|
||||||
|
rc := bufio.NewReader(f)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
lookupGroup.Lock()
|
||||||
|
defer lookupGroup.Unlock()
|
||||||
|
|
||||||
|
grp := parseNextGroup(rc)
|
||||||
|
for grp != nil {
|
||||||
|
if grp.name != groupname {
|
||||||
|
grp = parseNextGroup(rc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return grp.gid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
|
||||||
|
}
|
Reference in New Issue
Block a user