Skip to content

Commit

Permalink
use etchosts package from c/common
Browse files Browse the repository at this point in the history
Use the new etchosts package to generate the hosts file.
This will ensure that we use the same logic in podman and buildah.

New features are:
- no duplicated entries
- adds entries for the network/slirp4netns ips
- configure the host.containers.internal entry in containers.conf
- configure the base hosts file in containers.conf

Signed-off-by: Paul Holzinger <[email protected]>
  • Loading branch information
Luap99 committed Apr 21, 2022
1 parent faee129 commit 5de32ad
Show file tree
Hide file tree
Showing 9 changed files with 555 additions and 76 deletions.
100 changes: 51 additions & 49 deletions run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/sshagent"
"github.com/containers/buildah/util"
"github.com/containers/common/libnetwork/etchosts"
"github.com/containers/common/libnetwork/network"
nettypes "github.com/containers/common/libnetwork/types"
"github.com/containers/common/pkg/capabilities"
Expand Down Expand Up @@ -221,15 +222,13 @@ func (b *Builder) Run(command []string, options RunOptions) error {
}
rootIDPair := &idtools.IDPair{UID: int(rootUID), GID: int(rootGID)}

if !options.NoHosts && !contains(volumes, "/etc/hosts") {
hostFile, err := b.generateHosts(path, spec.Hostname, b.CommonBuildOpts.AddHost, rootIDPair)
hostFile := ""
if !options.NoHosts && !contains(volumes, config.DefaultHostsFile) && options.ConfigureNetwork != define.NetworkDisabled {
hostFile, err = b.generateHosts(path, rootIDPair, mountPoint)
if err != nil {
return err
}
// Only bind /etc/hosts if there's a network
if options.ConfigureNetwork != define.NetworkDisabled {
bindFiles["/etc/hosts"] = hostFile
}
bindFiles[config.DefaultHostsFile] = hostFile
}

if !(contains(volumes, "/etc/resolv.conf") || (len(b.CommonBuildOpts.DNSServers) == 1 && strings.ToLower(b.CommonBuildOpts.DNSServers[0]) == "none")) {
Expand Down Expand Up @@ -302,15 +301,17 @@ rootless=%d
if options.NoPivot {
moreCreateArgs = append(moreCreateArgs, "--no-pivot")
}
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, define.Package+"-"+filepath.Base(path))
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec,
mountPoint, path, define.Package+"-"+filepath.Base(path), b.Container, hostFile)
case IsolationChroot:
err = chroot.RunUsingChroot(spec, path, homeDir, options.Stdin, options.Stdout, options.Stderr)
case IsolationOCIRootless:
moreCreateArgs := []string{"--no-new-keyring"}
if options.NoPivot {
moreCreateArgs = append(moreCreateArgs, "--no-pivot")
}
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec, mountPoint, path, define.Package+"-"+filepath.Base(path))
err = b.runUsingRuntimeSubproc(isolation, options, configureNetwork, configureNetworks, moreCreateArgs, spec,
mountPoint, path, define.Package+"-"+filepath.Base(path), b.Container, hostFile)
default:
err = errors.Errorf("don't know how to run this command")
}
Expand Down Expand Up @@ -631,58 +632,41 @@ func (b *Builder) addResolvConf(rdir string, chownOpts *idtools.IDPair, dnsServe
}

// generateHosts creates a containers hosts file
func (b *Builder) generateHosts(rdir, hostname string, addHosts []string, chownOpts *idtools.IDPair) (string, error) {
hostPath := "/etc/hosts"
stat, err := os.Stat(hostPath)
func (b *Builder) generateHosts(rdir string, chownOpts *idtools.IDPair, imageRoot string) (string, error) {
conf, err := config.Default()
if err != nil {
return "", err
}

hosts := bytes.NewBufferString("# Generated by Buildah\n")
orig, err := ioutil.ReadFile(hostPath)
path, err := etchosts.GetBaseHostFile(conf.Containers.BaseHostsFile, imageRoot)
if err != nil {
return "", err
}
hosts.Write(orig)
for _, host := range addHosts {
// verify the host format
values := strings.SplitN(host, ":", 2)
if len(values) != 2 {
return "", errors.Errorf("unable to parse host entry %q: incorrect format", host)
}
if values[0] == "" {
return "", errors.Errorf("hostname in host entry %q is empty", host)
}
if values[1] == "" {
return "", errors.Errorf("IP address in host entry %q is empty", host)
}
hosts.Write([]byte(fmt.Sprintf("%s\t%s\n", values[1], values[0])))
}
hosts.Write([]byte(fmt.Sprintf("127.0.0.1 %s %s\n", b.Container, hostname)))
hosts.Write([]byte(fmt.Sprintf("::1 %s %s\n", b.Container, hostname)))

if ip := util.LocalIP(); ip != "" {
hosts.Write([]byte(fmt.Sprintf("%s %s\n", ip, "host.containers.internal")))
targetfile := filepath.Join(rdir, "hosts")
if err := etchosts.New(&etchosts.Params{
BaseFile: path,
ExtraHosts: b.CommonBuildOpts.AddHost,
HostContainersInternalIP: etchosts.GetHostContainersInternalIP(conf, nil, nil),
TargetFile: targetfile,
}); err != nil {
return "", err
}

cfile := filepath.Join(rdir, filepath.Base(hostPath))
if err = ioutils.AtomicWriteFile(cfile, hosts.Bytes(), stat.Mode().Perm()); err != nil {
return "", errors.Wrapf(err, "error writing /etc/hosts into the container")
}
uid := int(stat.Sys().(*syscall.Stat_t).Uid)
gid := int(stat.Sys().(*syscall.Stat_t).Gid)
uid := 0
gid := 0
if chownOpts != nil {
uid = chownOpts.UID
gid = chownOpts.GID
}
if err = os.Chown(cfile, uid, gid); err != nil {
if err = os.Chown(targetfile, uid, gid); err != nil {
return "", err
}
if err := label.Relabel(cfile, b.MountLabel, false); err != nil {
if err := label.Relabel(targetfile, b.MountLabel, false); err != nil {
return "", err
}

return cfile, nil
return targetfile, nil
}

func setupTerminal(g *generate.Generator, terminalPolicy TerminalPolicy, terminalSize *specs.Box) {
Expand Down Expand Up @@ -1105,9 +1089,10 @@ func setupRootlessNetwork(pid int) (teardown func(), err error) {
}, nil
}

func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, options RunOptions, configureNetworks []string, containerName string) (teardown func(), err error) {
func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, options RunOptions, configureNetworks []string, containerName string) (teardown func(), netStatus map[string]nettypes.StatusBlock, err error) {
if isolation == IsolationOCIRootless {
return setupRootlessNetwork(pid)
teardown, err = setupRootlessNetwork(pid)
return teardown, nil, err
}

if len(configureNetworks) == 0 {
Expand All @@ -1122,7 +1107,7 @@ func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, optio
netns := fmt.Sprintf("/proc/%d/ns/net", pid)
netFD, err := unix.Open(netns, unix.O_RDONLY, 0)
if err != nil {
return nil, errors.Wrapf(err, "error opening network namespace")
return nil, nil, errors.Wrapf(err, "error opening network namespace")
}
mynetns := fmt.Sprintf("/proc/%d/fd/%d", unix.Getpid(), netFD)

Expand All @@ -1138,9 +1123,9 @@ func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, optio
ContainerName: containerName,
Networks: networks,
}
_, err = b.NetworkInterface.Setup(mynetns, nettypes.SetupOptions{NetworkOptions: opts})
netStatus, err = b.NetworkInterface.Setup(mynetns, nettypes.SetupOptions{NetworkOptions: opts})
if err != nil {
return nil, err
return nil, nil, err
}

teardown = func() {
Expand All @@ -1150,7 +1135,7 @@ func (b *Builder) runConfigureNetwork(pid int, isolation define.Isolation, optio
}
}

return teardown, nil
return teardown, netStatus, nil
}

func setNonblock(logger *logrus.Logger, fd int, description string, nonblocking bool) (bool, error) { //nolint:interfacer
Expand Down Expand Up @@ -2208,7 +2193,8 @@ func checkIdsGreaterThan5(ids []spec.LinuxIDMapping) bool {
return false
}

func (b *Builder) runUsingRuntimeSubproc(isolation define.Isolation, options RunOptions, configureNetwork bool, configureNetworks, moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName string) (err error) {
func (b *Builder) runUsingRuntimeSubproc(isolation define.Isolation, options RunOptions, configureNetwork bool, configureNetworks,
moreCreateArgs []string, spec *specs.Spec, rootPath, bundlePath, containerName, buildContainerName, hostsFile string) (err error) {
var confwg sync.WaitGroup
config, conferr := json.Marshal(runUsingRuntimeSubprocOptions{
Options: options,
Expand Down Expand Up @@ -2309,14 +2295,30 @@ func (b *Builder) runUsingRuntimeSubproc(isolation define.Isolation, options Run
return errors.Wrapf(err, "error parsing pid %s as a number", string(pidValue))
}

teardown, err := b.runConfigureNetwork(pid, isolation, options, configureNetworks, containerName)
teardown, netstatus, err := b.runConfigureNetwork(pid, isolation, options, configureNetworks, containerName)
if teardown != nil {
defer teardown()
}
if err != nil {
return err
}

// only add hosts if we manage the hosts file
if hostsFile != "" {
var entries etchosts.HostEntries
if netstatus != nil {
entries = etchosts.GetNetworkHostEntries(netstatus, spec.Hostname, buildContainerName)
} else {
// we have slirp4netns, default to slirp4netns ip since this is not configurable in buildah
entries = etchosts.HostEntries{{IP: "10.0.2.100", Names: []string{spec.Hostname, buildContainerName}}}
}
// make sure to sync this with (b *Builder) generateHosts()
err = etchosts.Add(hostsFile, entries)
if err != nil {
return err
}
}

logrus.Debug("network namespace successfully setup, send start message to child")
_, err = containerStartW.file.Write([]byte{1})
if err != nil {
Expand Down
13 changes: 9 additions & 4 deletions tests/from.bats
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ load helpers
run_buildah from --quiet --add-host=localhost:127.0.0.1 --pull --signature-policy ${TESTSDIR}/policy.json alpine
cid=$output
run_buildah run --net=container $cid -- cat /etc/hosts
expect_output --substring "127.0.0.1 +localhost"
expect_output --substring "127.0.0.1[[:blank:]]*localhost"
}

@test "from name test" {
Expand Down Expand Up @@ -649,6 +649,9 @@ load helpers
}

@test "from-image-by-id" {
skip_if_chroot
skip_if_no_runtime

_prefetch busybox
run_buildah from --cidfile ${TESTDIR}/cid busybox
cid=$(cat ${TESTDIR}/cid)
Expand All @@ -658,8 +661,10 @@ load helpers
iid=$(cat ${TESTDIR}/iid)
run_buildah from --cidfile ${TESTDIR}/cid2 ${iid}
cid2=$(cat ${TESTDIR}/cid2)
run_buildah run ${cid2} hostname -f
run_buildah run ${cid2} cat /etc/hosts
truncated=${iid##*:}
truncated=$(echo ${truncated} | cut -c-12)
expect_output ${truncated}-working-container
truncated="${truncated:0:12}"
expect_output --substring ${truncated}-working-container
run_buildah run ${cid2} hostname -f
expect_output "${cid2:0:12}"
}
12 changes: 12 additions & 0 deletions tests/helpers.bash
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ function createrandom() {
dd if=/dev/urandom bs=1 count=${2:-256} of=${1:-${BATS_TMPDIR}/randomfile} status=none
}

###################
# random_string # Returns a pseudorandom human-readable string
###################
#
# Numeric argument, if present, is desired length of string
#
function random_string() {
local length=${1:-10}

head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
}

function buildah() {
${BUILDAH_BINARY} ${BUILDAH_REGISTRY_OPTS} ${ROOTDIR_OPTS} "$@"
}
Expand Down
27 changes: 22 additions & 5 deletions tests/run.bats
Original file line number Diff line number Diff line change
Expand Up @@ -591,18 +591,36 @@ function configure_and_check_user() {
${OCI} --version
_prefetch debian

local hostname=h-$(random_string)

run_buildah from --quiet --pull=false --signature-policy ${TESTSDIR}/policy.json debian
cid=$output
run_buildah 125 run --network=bogus $cid cat /etc/hosts
expect_output --substring "unable to find network with name or ID bogus: network not found"
run_buildah run $cid cat /etc/hosts
expect_output --substring "127.0.0.1.*$cid"
expect_output --substring "::1.*$cid"
run_buildah run --hostname $hostname $cid cat /etc/hosts
expect_output --substring "(10.88.*|10.0.2.100)[[:blank:]]$hostname $cid"
ip=$(hostname -I | cut -f 1 -d " ")
expect_output --substring "$ip.*host.containers.internal"

hosts="127.0.0.5 host1
127.0.0.6 host2"
base_hosts_file="$TESTDIR/base_hosts"
echo "$hosts" > "$base_hosts_file"
containers_conf_file="$TESTDIR/containers.conf"
echo -e "[containers]\nbase_hosts_file = \"$base_hosts_file\"" > "$containers_conf_file"
CONTAINERS_CONF="$containers_conf_file" run_buildah run --hostname $hostname $cid cat /etc/hosts
expect_output --substring "127.0.0.5[[:blank:]]host1"
expect_output --substring "127.0.0.6[[:blank:]]host2"
expect_output --substring "(10.88.*|10.0.2.100)[[:blank:]]$hostname $cid"

# now check that hostname from base file is not overwritten
CONTAINERS_CONF="$containers_conf_file" run_buildah run --hostname host1 $cid cat /etc/hosts
expect_output --substring "127.0.0.5[[:blank:]]host1"
expect_output --substring "127.0.0.6[[:blank:]]host2"
expect_output --substring "(10.88.*|10.0.2.100)[[:blank:]]$cid"
assert "$output" !~ "(10.88.*|10.0.2.100)[[:blank:]]host1 $cid" "Container IP should not contain host1"

run_buildah run --network=container $cid cat /etc/hosts
expect_output --substring "# Generated by Buildah"
m=$(buildah mount $cid)
run cat $m/etc/hosts
[ "$status" -eq 0 ]
Expand All @@ -613,7 +631,6 @@ function configure_and_check_user() {
cid=$output
run_buildah run --network=host $cid cat /etc/hosts
hostOutput=$output
expect_output --substring "# Generated by Buildah"
m=$(buildah mount $cid)
run cat $m/etc/hosts
[ "$status" -eq 0 ]
Expand Down
18 changes: 0 additions & 18 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package util
import (
"fmt"
"io"
"net"
"net/url"
"os"
"path/filepath"
Expand Down Expand Up @@ -466,20 +465,3 @@ func VerifyTagName(imageSpec string) (types.ImageReference, error) {
}
return ref, nil
}

// LocalIP returns the non loopback local IP of the host
func LocalIP() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
return ""
}
for _, address := range addrs {
// check the address type and if it is not a loopback the display it
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String()
}
}
}
return ""
}
Loading

0 comments on commit 5de32ad

Please sign in to comment.