Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

containerd: properly populate /etc/hosts and /etc/hostname #7041

Merged
merged 2 commits into from May 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion worker/runtime/backend.go
Expand Up @@ -246,7 +246,7 @@ func (b *GardenBackend) startTask(ctx context.Context, cont containerd.Container
return fmt.Errorf("new task: %w", err)
}

err = b.network.Add(ctx, task)
err = b.network.Add(ctx, task, cont.ID())
if err != nil {
return fmt.Errorf("network add: %w", err)
}
Expand Down
33 changes: 29 additions & 4 deletions worker/runtime/cni_network.go
Expand Up @@ -258,12 +258,20 @@ func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {

etcHosts, err := n.store.Create(
filepath.Join(handle, "/hosts"),
[]byte("127.0.0.1 localhost"),
[]byte("127.0.0.1 localhost\n"),
)
if err != nil {
return nil, fmt.Errorf("creating /etc/hosts: %w", err)
}

etcHostName, err := n.store.Create(
filepath.Join(handle, "/hostname"),
[]byte(handle+"\n"),
)
if err != nil {
return nil, fmt.Errorf("creating /etc/hostname: %w", err)
}

resolvContents, err := n.generateResolvConfContents()
if err != nil {
return nil, fmt.Errorf("generating resolv.conf: %w", err)
Expand All @@ -283,6 +291,11 @@ func (n cniNetwork) SetupMounts(handle string) ([]specs.Mount, error) {
Type: "bind",
Source: etcHosts,
Options: []string{"bind", "rw"},
}, {
Destination: "/etc/hostname",
Type: "bind",
Source: etcHostName,
Options: []string{"bind", "rw"},
}, {
Destination: "/etc/resolv.conf",
Type: "bind",
Expand Down Expand Up @@ -344,19 +357,31 @@ func (n cniNetwork) restrictHostAccess() error {
return nil
}

func (n cniNetwork) Add(ctx context.Context, task containerd.Task) error {
func (n cniNetwork) Add(ctx context.Context, task containerd.Task, containerHandle string) error {
if task == nil {
return ErrInvalidInput("nil task")
}

id, netns := netId(task), netNsPath(task)

_, err := n.client.Setup(ctx, id, netns)
result, err := n.client.Setup(ctx, id, netns)

if err != nil {
return fmt.Errorf("cni net setup: %w", err)
}

return nil
// Find container IP
config, found := result.Interfaces["eth0"]
if !found || len(config.IPConfigs) == 0 {
return fmt.Errorf("cni net setup: no eth0 interface found")
}

// Update /etc/hosts on container
// This could not be done earlier because we only have the container IP after the network has been setup
return n.store.Append(
filepath.Join(containerHandle, "/hosts"),
[]byte(config.IPConfigs[0].IP.String()+" "+containerHandle+"\n"),
)
}

func (n cniNetwork) Remove(ctx context.Context, task containerd.Task) error {
Expand Down
68 changes: 58 additions & 10 deletions worker/runtime/cni_network_test.go
Expand Up @@ -3,9 +3,12 @@ package runtime_test
import (
"context"
"errors"
"net"
"reflect"
"strings"

"github.com/containerd/go-cni"

"github.com/concourse/concourse/worker/runtime"
"github.com/concourse/concourse/worker/runtime/iptables/iptablesfakes"
"github.com/concourse/concourse/worker/runtime/libcontainerd/libcontainerdfakes"
Expand Down Expand Up @@ -70,33 +73,52 @@ func (s *CNINetworkSuite) TestSetupMountsFailToCreateHosts() {
s.Equal("handle/hosts", fname)
}

func (s *CNINetworkSuite) TestSetupMountsFailToCreateResolvConf() {
s.store.CreateReturnsOnCall(1, "", errors.New("create-resolvconf-err"))
func (s *CNINetworkSuite) TestSetupMountsFailToCreateHostname() {
s.store.CreateReturnsOnCall(1, "", errors.New("create-hostname-err"))

_, err := s.network.SetupMounts("handle")
s.EqualError(errors.Unwrap(err), "create-resolvconf-err")
s.EqualError(errors.Unwrap(err), "create-hostname-err")

s.Equal(2, s.store.CreateCallCount())
fname, _ := s.store.CreateArgsForCall(1)

s.Equal("handle/hostname", fname)
}

func (s *CNINetworkSuite) TestSetupMountsFailToCreateResolvConf() {
s.store.CreateReturnsOnCall(2, "", errors.New("create-resolvconf-err"))

_, err := s.network.SetupMounts("handle")
s.EqualError(errors.Unwrap(err), "create-resolvconf-err")

s.Equal(3, s.store.CreateCallCount())
fname, _ := s.store.CreateArgsForCall(2)

s.Equal("handle/resolv.conf", fname)
}

func (s *CNINetworkSuite) TestSetupMountsReturnsMountpoints() {
s.store.CreateReturnsOnCall(0, "/tmp/handle/etc/hosts", nil)
s.store.CreateReturnsOnCall(1, "/tmp/handle/etc/resolv.conf", nil)
s.store.CreateReturnsOnCall(1, "/tmp/handle/etc/hostname", nil)
s.store.CreateReturnsOnCall(2, "/tmp/handle/etc/resolv.conf", nil)

mounts, err := s.network.SetupMounts("some-handle")
s.NoError(err)

s.Len(mounts, 2)
s.Len(mounts, 3)
s.Equal(mounts, []specs.Mount{
{
Destination: "/etc/hosts",
Type: "bind",
Source: "/tmp/handle/etc/hosts",
Options: []string{"bind", "rw"},
},
{
Destination: "/etc/hostname",
Type: "bind",
Source: "/tmp/handle/etc/hostname",
Options: []string{"bind", "rw"},
},
{
Destination: "/etc/resolv.conf",
Type: "bind",
Expand All @@ -117,7 +139,7 @@ func (s *CNINetworkSuite) TestSetupMountsCallsStoreWithNameServers() {
_, err = network.SetupMounts("some-handle")
s.NoError(err)

_, resolvConfContents := s.store.CreateArgsForCall(1)
_, resolvConfContents := s.store.CreateArgsForCall(2)
s.Equal(resolvConfContents, []byte("nameserver 6.6.7.7\nnameserver 1.2.3.4\n"))
}

Expand All @@ -136,7 +158,7 @@ func (s *CNINetworkSuite) TestSetupMountsCallsStoreWithoutNameServers() {

contents := strings.Join(actualResolvContents, "\n") + "\n"

_, resolvConfContents := s.store.CreateArgsForCall(1)
_, resolvConfContents := s.store.CreateArgsForCall(2)
s.Equal(resolvConfContents, []byte(contents))
}

Expand Down Expand Up @@ -245,24 +267,50 @@ func (s *CNINetworkSuite) TestSetupHostNetwork() {
}

func (s *CNINetworkSuite) TestAddNilTask() {
err := s.network.Add(context.Background(), nil)
err := s.network.Add(context.Background(), nil, "container-handle")
s.EqualError(err, "nil task")
}

func (s *CNINetworkSuite) TestAddSetupErrors() {
s.cni.SetupReturns(nil, errors.New("setup-err"))
task := new(libcontainerdfakes.FakeTask)

err := s.network.Add(context.Background(), task)
err := s.network.Add(context.Background(), task, "container-handle")
s.EqualError(errors.Unwrap(err), "setup-err")
}

func (s *CNINetworkSuite) TestAddInterfaceNotFound() {
task := new(libcontainerdfakes.FakeTask)
task.PidReturns(123)
task.IDReturns("id")

result := &cni.Result{
Interfaces: make(map[string]*cni.Config, 0),
}
s.cni.SetupReturns(result, nil)
err := s.network.Add(context.Background(), task, "container-handle")
s.EqualError(err, "cni net setup: no eth0 interface found")
}

func (s *CNINetworkSuite) TestAdd() {
task := new(libcontainerdfakes.FakeTask)
task.PidReturns(123)
task.IDReturns("id")

err := s.network.Add(context.Background(), task)
result := &cni.Result{
Interfaces: make(map[string]*cni.Config, 0),
}
result.Interfaces["eth0"] = &cni.Config{
IPConfigs: []*cni.IPConfig{
{
IP: net.IPv4(10, 8, 0, 1),
},
},
}

s.cni.SetupReturns(result, nil)

err := s.network.Add(context.Background(), task, "container-handle")
s.NoError(err)

s.Equal(1, s.cni.SetupCallCount())
Expand Down
22 changes: 22 additions & 0 deletions worker/runtime/file_store.go
Expand Up @@ -16,6 +16,10 @@ type FileStore interface {
//
Create(name string, content []byte) (absPath string, err error)

// Append appends to a file previously created in the store.
//
Append(name string, content []byte) error

// DeleteFile removes a file previously created in the store.
//
Delete(name string) (err error)
Expand Down Expand Up @@ -50,6 +54,24 @@ func (f fileStore) Create(name string, content []byte) (string, error) {
return absPath, nil
}

func (f fileStore) Append(name string, content []byte) error {
absPath := filepath.Join(f.root, name)

file, err := os.OpenFile(absPath, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("open file: %w", err)
}
defer file.Close()

if _, err := file.Write(content); err != nil {
return fmt.Errorf("write file: %w", err)
}

return nil
}



func (f fileStore) Delete(path string) error {
absPath := filepath.Join(f.root, path)

Expand Down
11 changes: 11 additions & 0 deletions worker/runtime/file_store_test.go
Expand Up @@ -49,6 +49,17 @@ func (s *FileStoreSuite) TestCreateFileInDir() {
s.Equal("hey", string(content))
}

func (s *FileStoreSuite) TestAppendFile() {
fpath, err := s.store.Create("dir/name", []byte("hey"))
s.NoError(err)

err = s.store.Append("dir/name", []byte(" there"))
s.NoError(err)
content, err := ioutil.ReadFile(fpath)
s.NoError(err)
s.Equal("hey there", string(content))
}

func (s *FileStoreSuite) TestDeleteFile() {
fpath, err := s.store.Create("dir/name", []byte("hey"))
s.NoError(err)
Expand Down
40 changes: 39 additions & 1 deletion worker/runtime/integration/integration_test.go
Expand Up @@ -369,7 +369,7 @@ func (s *IntegrationSuite) TestContainerAllowsHostAccess() {
s.gardenBackend.Stop()
s.cleanupIptables()

namespace := "test-block-host-access"
namespace := "test-allow-host-access"
requestTimeout := 3 * time.Second

network, err := runtime.NewCNINetwork(runtime.WithAllowHostAccess())
Expand Down Expand Up @@ -434,6 +434,44 @@ func (s *IntegrationSuite) TestContainerAllowsHostAccess() {
s.Equal(exitCode, 0, "Process in container should be able to reach the host network")
}

func (s *IntegrationSuite) TestContainerNetworkHosts() {
s.NoError(s.gardenBackend.Start())

handle := uuid()

container, err := s.gardenBackend.Create(garden.ContainerSpec{
Handle: handle,
RootFSPath: "raw://" + s.rootfs,
Privileged: true,
})
s.NoError(err)

defer func() {
s.NoError(s.gardenBackend.Destroy(handle))
}()

buf := new(buffer)
proc, err := container.Run(
garden.ProcessSpec{
Path: "/executable",
Args: []string{
"-cat=/etc/hosts",
},
},
garden.ProcessIO{
Stdout: buf,
Stderr: buf,
},
)
s.NoError(err)

exitCode, err := proc.Wait()
s.NoError(err)

s.Equal(exitCode, 0)
s.Contains(buf.String(), handle)
}

// TestRunPrivileged tests whether we're able to run a process in a privileged
// container.
//
Expand Down
2 changes: 1 addition & 1 deletion worker/runtime/network.go
Expand Up @@ -21,7 +21,7 @@ type Network interface {

// Add adds a task to the network.
//
Add(ctx context.Context, task containerd.Task) (err error)
Add(ctx context.Context, task containerd.Task, containerHandle string) (err error)

// Removes a task from the network.
//
Expand Down