libpod should know if the network is disabled

/etc/resolv.conf and /etc/hosts should not be created and mounted when the
network is disabled.

We should not be calling the network setup and cleanup functions when it is
disabled either.

In doing this patch, I found that all of the bind mounts were particular to
Linux along with the generate functions, so I moved them to
container_internal_linux.go

Since we are checking if we are using a network namespace, we need to check
after the network namespaces has been created in the spec.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh
2018-11-08 06:12:14 -05:00
parent 900436e70f
commit bb6c1cf8d1
4 changed files with 253 additions and 227 deletions

View File

@ -670,6 +670,11 @@ func parseCreateOpts(ctx context.Context, c *cli.Context, runtime *libpod.Runtim
if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 {
return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
}
if !netMode.IsPrivate() {
if c.IsSet("dns-search") || c.IsSet("dns") || c.IsSet("dns-opt") {
return nil, errors.Errorf("specifying DNS flags when network mode is shared with the host or another container is not allowed")
}
}
// Validate domains are good
for _, dom := range c.StringSlice("dns-search") {

View File

@ -996,3 +996,15 @@ func (c *Container) IsInfra() bool {
func (c *Container) IsReadOnly() bool {
return c.config.Spec.Root.Readonly
}
// NetworkDisabled returns whether the container is running with a disabled network
func (c *Container) NetworkDisabled() bool {
if !c.config.PostConfigureNetNS {
for _, ns := range c.config.Spec.Linux.Namespaces {
if ns.Type == spec.NetworkNamespace {
return ns.Path == ""
}
}
}
return false
}

View File

@ -19,14 +19,11 @@ import (
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
"github.com/containers/libpod/pkg/lookup"
"github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/secrets"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/mount"
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@ -583,7 +580,7 @@ func (c *Container) checkDependenciesRunningLocked(depCtrs map[string]*Container
}
func (c *Container) completeNetworkSetup() error {
if !c.config.PostConfigureNetNS {
if !c.config.PostConfigureNetNS || c.NetworkDisabled() {
return nil
}
if err := c.syncContainer(); err != nil {
@ -597,10 +594,6 @@ func (c *Container) completeNetworkSetup() error {
// Initialize a container, creating it in the runtime
func (c *Container) init(ctx context.Context) error {
if err := c.makeBindMounts(); err != nil {
return err
}
// Generate the OCI spec
spec, err := c.generateSpec(ctx)
if err != nil {
@ -987,86 +980,6 @@ func (c *Container) postDeleteHooks(ctx context.Context) (err error) {
return nil
}
// Make standard bind mounts to include in the container
func (c *Container) makeBindMounts() error {
if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir)
}
if c.state.BindMounts == nil {
c.state.BindMounts = make(map[string]string)
}
// SHM is always added when we mount the container
c.state.BindMounts["/dev/shm"] = c.config.ShmDir
// Make /etc/resolv.conf
if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/resolv.conf")
}
newResolv, err := c.generateResolvConf()
if err != nil {
return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
}
c.state.BindMounts["/etc/resolv.conf"] = newResolv
newPasswd, err := c.generatePasswd()
if err != nil {
return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID())
}
if newPasswd != "" {
// Make /etc/passwd
if _, ok := c.state.BindMounts["/etc/passwd"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/passwd")
}
logrus.Debugf("adding entry to /etc/passwd for non existent default user")
c.state.BindMounts["/etc/passwd"] = newPasswd
}
// Make /etc/hosts
if _, ok := c.state.BindMounts["/etc/hosts"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/hosts")
}
newHosts, err := c.generateHosts()
if err != nil {
return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
}
c.state.BindMounts["/etc/hosts"] = newHosts
// Make /etc/hostname
// This should never change, so no need to recreate if it exists
if _, ok := c.state.BindMounts["/etc/hostname"]; !ok {
hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname())
if err != nil {
return errors.Wrapf(err, "error creating hostname file for container %s", c.ID())
}
c.state.BindMounts["/etc/hostname"] = hostnamePath
}
// Make .containerenv
// Empty file, so no need to recreate if it exists
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
// Empty string for now, but we may consider populating this later
containerenvPath, err := c.writeStringToRundir(".containerenv", "")
if err != nil {
return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
}
c.state.BindMounts["/run/.containerenv"] = containerenvPath
}
// Add Secret Mounts
secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID())
for _, mount := range secretMounts {
if _, ok := c.state.BindMounts[mount.Destination]; !ok {
c.state.BindMounts[mount.Destination] = mount.Source
}
}
return nil
}
// writeStringToRundir copies the provided file to the runtimedir
func (c *Container) writeStringToRundir(destFile, output string) (string, error) {
destFileName := filepath.Join(c.state.RunDir, destFile)
@ -1095,145 +1008,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
// generatePasswd generates a container specific passwd file,
// iff g.config.User is a number
func (c *Container) generatePasswd() (string, error) {
var (
groupspec string
group *user.Group
gid int
)
if c.config.User == "" {
return "", nil
}
spec := strings.SplitN(c.config.User, ":", 2)
userspec := spec[0]
if len(spec) > 1 {
groupspec = spec[1]
}
// If a non numeric User, then don't generate passwd
uid, err := strconv.ParseUint(userspec, 10, 32)
if err != nil {
return "", nil
}
// Lookup the user to see if it exists in the container image
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
if err != nil && err != user.ErrNoPasswdEntries {
return "", err
}
if err == nil {
return "", nil
}
if groupspec != "" {
if !c.state.Mounted {
return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID())
}
group, err = lookup.GetGroup(c.state.Mountpoint, groupspec)
if err != nil {
if err == user.ErrNoGroupEntries {
return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
}
return "", err
}
gid = group.Gid
}
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
orig, err := ioutil.ReadFile(originPasswdFile)
if err != nil && !os.IsNotExist(err) {
return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
}
pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir())
passwdFile, err := c.writeStringToRundir("passwd", pwd)
if err != nil {
return "", errors.Wrapf(err, "failed to create temporary passwd file")
}
if os.Chmod(passwdFile, 0644); err != nil {
return "", err
}
return passwdFile, nil
}
// generateResolvConf generates a containers resolv.conf
func (c *Container) generateResolvConf() (string, error) {
// Determine the endpoint for resolv.conf in case it is a symlink
resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf")
if err != nil {
return "", err
}
contents, err := ioutil.ReadFile(resolvPath)
if err != nil {
return "", errors.Wrapf(err, "unable to read %s", resolvPath)
}
// Process the file to remove localhost nameservers
// TODO: set ipv6 enable bool more sanely
resolv, err := resolvconf.FilterResolvDNS(contents, true)
if err != nil {
return "", errors.Wrapf(err, "error parsing host resolv.conf")
}
// Make a new resolv.conf
nameservers := resolvconf.GetNameservers(resolv.Content)
if len(c.config.DNSServer) > 0 {
// We store DNS servers as net.IP, so need to convert to string
nameservers = []string{}
for _, server := range c.config.DNSServer {
nameservers = append(nameservers, server.String())
}
}
search := resolvconf.GetSearchDomains(resolv.Content)
if len(c.config.DNSSearch) > 0 {
search = c.config.DNSSearch
}
options := resolvconf.GetOptions(resolv.Content)
if len(c.config.DNSOption) > 0 {
options = c.config.DNSOption
}
destPath := filepath.Join(c.state.RunDir, "resolv.conf")
if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID())
}
// Build resolv.conf
if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
return "", errors.Wrapf(err, "error building resolv.conf for container %s")
}
// Relabel resolv.conf for the container
if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil {
return "", err
}
return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil
}
// generateHosts creates a containers hosts file
func (c *Container) generateHosts() (string, error) {
orig, err := ioutil.ReadFile("/etc/hosts")
if err != nil {
return "", errors.Wrapf(err, "unable to read /etc/hosts")
}
hosts := string(orig)
if len(c.config.HostAdd) > 0 {
for _, host := range c.config.HostAdd {
// the host format has already been verified at this point
fields := strings.SplitN(host, ":", 2)
hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0])
}
}
if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname())
}
return c.writeStringToRundir("hosts", hosts)
}
func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator) error {
var uid, gid int
mountPoint := c.state.Mountpoint

View File

@ -11,6 +11,7 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
@ -21,8 +22,11 @@ import (
crioAnnotations "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/criu"
"github.com/containers/libpod/pkg/lookup"
"github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/secrets"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@ -132,6 +136,9 @@ func (c *Container) prepare() (err error) {
// cleanupNetwork unmounts and cleans up the container's network
func (c *Container) cleanupNetwork() error {
if c.NetworkDisabled() {
return nil
}
if c.state.NetNS == nil {
logrus.Debugf("Network is already cleaned up, skipping...")
return nil
@ -169,6 +176,11 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path())
}
}
if err := c.makeBindMounts(); err != nil {
return nil, err
}
// Check if the spec file mounts contain the label Relabel flags z or Z.
// If they do, relabel the source directory and then remove the option.
for _, m := range g.Mounts() {
@ -591,3 +603,226 @@ func (c *Container) restore(ctx context.Context, keep bool) (err error) {
return c.save()
}
// Make standard bind mounts to include in the container
func (c *Container) makeBindMounts() error {
if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
return errors.Wrapf(err, "cannot chown run directory %s", c.state.RunDir)
}
if c.state.BindMounts == nil {
c.state.BindMounts = make(map[string]string)
}
if !c.NetworkDisabled() {
// Make /etc/resolv.conf
if _, ok := c.state.BindMounts["/etc/resolv.conf"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/resolv.conf")
}
newResolv, err := c.generateResolvConf()
if err != nil {
return errors.Wrapf(err, "error creating resolv.conf for container %s", c.ID())
}
c.state.BindMounts["/etc/resolv.conf"] = newResolv
// Make /etc/hosts
if _, ok := c.state.BindMounts["/etc/hosts"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/hosts")
}
newHosts, err := c.generateHosts()
if err != nil {
return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
}
c.state.BindMounts["/etc/hosts"] = newHosts
}
// SHM is always added when we mount the container
c.state.BindMounts["/dev/shm"] = c.config.ShmDir
newPasswd, err := c.generatePasswd()
if err != nil {
return errors.Wrapf(err, "error creating temporary passwd file for container %s", c.ID())
}
if newPasswd != "" {
// Make /etc/passwd
if _, ok := c.state.BindMounts["/etc/passwd"]; ok {
// If it already exists, delete so we can recreate
delete(c.state.BindMounts, "/etc/passwd")
}
logrus.Debugf("adding entry to /etc/passwd for non existent default user")
c.state.BindMounts["/etc/passwd"] = newPasswd
}
// Make /etc/hostname
// This should never change, so no need to recreate if it exists
if _, ok := c.state.BindMounts["/etc/hostname"]; !ok {
hostnamePath, err := c.writeStringToRundir("hostname", c.Hostname())
if err != nil {
return errors.Wrapf(err, "error creating hostname file for container %s", c.ID())
}
c.state.BindMounts["/etc/hostname"] = hostnamePath
}
// Make .containerenv
// Empty file, so no need to recreate if it exists
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
// Empty string for now, but we may consider populating this later
containerenvPath, err := c.writeStringToRundir(".containerenv", "")
if err != nil {
return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
}
c.state.BindMounts["/run/.containerenv"] = containerenvPath
}
// Add Secret Mounts
secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.DefaultMountsFile, c.state.DestinationRunDir, c.RootUID(), c.RootGID())
for _, mount := range secretMounts {
if _, ok := c.state.BindMounts[mount.Destination]; !ok {
c.state.BindMounts[mount.Destination] = mount.Source
}
}
return nil
}
// generateResolvConf generates a containers resolv.conf
func (c *Container) generateResolvConf() (string, error) {
// Determine the endpoint for resolv.conf in case it is a symlink
resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf")
if err != nil {
return "", err
}
contents, err := ioutil.ReadFile(resolvPath)
if err != nil {
return "", errors.Wrapf(err, "unable to read %s", resolvPath)
}
// Process the file to remove localhost nameservers
// TODO: set ipv6 enable bool more sanely
resolv, err := resolvconf.FilterResolvDNS(contents, true)
if err != nil {
return "", errors.Wrapf(err, "error parsing host resolv.conf")
}
// Make a new resolv.conf
nameservers := resolvconf.GetNameservers(resolv.Content)
if len(c.config.DNSServer) > 0 {
// We store DNS servers as net.IP, so need to convert to string
nameservers = []string{}
for _, server := range c.config.DNSServer {
nameservers = append(nameservers, server.String())
}
}
search := resolvconf.GetSearchDomains(resolv.Content)
if len(c.config.DNSSearch) > 0 {
search = c.config.DNSSearch
}
options := resolvconf.GetOptions(resolv.Content)
if len(c.config.DNSOption) > 0 {
options = c.config.DNSOption
}
destPath := filepath.Join(c.state.RunDir, "resolv.conf")
if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID())
}
// Build resolv.conf
if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
return "", errors.Wrapf(err, "error building resolv.conf for container %s")
}
// Relabel resolv.conf for the container
if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil {
return "", err
}
return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil
}
// generateHosts creates a containers hosts file
func (c *Container) generateHosts() (string, error) {
orig, err := ioutil.ReadFile("/etc/hosts")
if err != nil {
return "", errors.Wrapf(err, "unable to read /etc/hosts")
}
hosts := string(orig)
if len(c.config.HostAdd) > 0 {
for _, host := range c.config.HostAdd {
// the host format has already been verified at this point
fields := strings.SplitN(host, ":", 2)
hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0])
}
}
if len(c.state.NetworkStatus) > 0 && len(c.state.NetworkStatus[0].IPs) > 0 {
ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0]
hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname())
}
return c.writeStringToRundir("hosts", hosts)
}
// generatePasswd generates a container specific passwd file,
// iff g.config.User is a number
func (c *Container) generatePasswd() (string, error) {
var (
groupspec string
group *user.Group
gid int
)
if c.config.User == "" {
return "", nil
}
spec := strings.SplitN(c.config.User, ":", 2)
userspec := spec[0]
if len(spec) > 1 {
groupspec = spec[1]
}
// If a non numeric User, then don't generate passwd
uid, err := strconv.ParseUint(userspec, 10, 32)
if err != nil {
return "", nil
}
// Lookup the user to see if it exists in the container image
_, err = lookup.GetUser(c.state.Mountpoint, userspec)
if err != nil && err != user.ErrNoPasswdEntries {
return "", err
}
if err == nil {
return "", nil
}
if groupspec != "" {
if !c.state.Mounted {
return "", errors.Wrapf(ErrCtrStateInvalid, "container %s must be mounted in order to translate group field for passwd record", c.ID())
}
group, err = lookup.GetGroup(c.state.Mountpoint, groupspec)
if err != nil {
if err == user.ErrNoGroupEntries {
return "", errors.Wrapf(err, "unable to get gid %s from group file", groupspec)
}
return "", err
}
gid = group.Gid
}
originPasswdFile := filepath.Join(c.state.Mountpoint, "/etc/passwd")
orig, err := ioutil.ReadFile(originPasswdFile)
if err != nil && !os.IsNotExist(err) {
return "", errors.Wrapf(err, "unable to read passwd file %s", originPasswdFile)
}
pwd := fmt.Sprintf("%s%d:x:%d:%d:container user:%s:/bin/sh\n", orig, uid, uid, gid, c.WorkingDir())
passwdFile, err := c.writeStringToRundir("passwd", pwd)
if err != nil {
return "", errors.Wrapf(err, "failed to create temporary passwd file")
}
if os.Chmod(passwdFile, 0644); err != nil {
return "", err
}
return passwdFile, nil
}