Use path based unix socket for shims
This allows filesystem based ACLs for configuring access to the socket of a shim. Co-authored-by: Samuel Karp <skarp@amazon.com> Signed-off-by: Samuel Karp <skarp@amazon.com> Signed-off-by: Michael Crosby <michael@thepasture.io> Signed-off-by: Michael Crosby <michael.crosby@apple.com>
This commit is contained in:
parent
d852786705
commit
bd908acabd
@ -24,6 +24,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||||
@ -240,10 +241,11 @@ func getTaskService(context *cli.Context) (task.TaskService, error) {
|
|||||||
s1 := filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock")
|
s1 := filepath.Join(string(filepath.Separator), "containerd-shim", ns, id, "shim.sock")
|
||||||
// this should not error, ctr always get a default ns
|
// this should not error, ctr always get a default ns
|
||||||
ctx := namespaces.WithNamespace(gocontext.Background(), ns)
|
ctx := namespaces.WithNamespace(gocontext.Background(), ns)
|
||||||
s2, _ := shim.SocketAddress(ctx, id)
|
s2, _ := shim.SocketAddress(ctx, context.GlobalString("address"), id)
|
||||||
|
s2 = strings.TrimPrefix(s2, "unix://")
|
||||||
|
|
||||||
for _, socket := range []string{s1, s2} {
|
for _, socket := range []string{s2, "\x00" + s1} {
|
||||||
conn, err := net.Dial("unix", "\x00"+socket)
|
conn, err := net.Dial("unix", socket)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
client := ttrpc.NewClient(conn)
|
client := ttrpc.NewClient(conn)
|
||||||
|
|
||||||
|
@ -131,20 +131,26 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
address, err := shim.SocketAddress(ctx, id)
|
address, err := shim.SocketAddress(ctx, containerdAddress, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
socket, err := shim.NewSocket(address)
|
socket, err := shim.NewSocket(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
if !shim.SocketEaddrinuse(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := shim.RemoveSocket(address); err != nil {
|
||||||
|
return "", errors.Wrap(err, "remove already used socket")
|
||||||
|
}
|
||||||
|
if socket, err = shim.NewSocket(address); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer socket.Close()
|
|
||||||
f, err := socket.File()
|
f, err := socket.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
||||||
|
|
||||||
@ -153,6 +159,7 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
|
|||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
_ = shim.RemoveSocket(address)
|
||||||
cmd.Process.Kill()
|
cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -551,6 +558,9 @@ func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*task
|
|||||||
func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) {
|
func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) {
|
||||||
s.cancel()
|
s.cancel()
|
||||||
close(s.events)
|
close(s.events)
|
||||||
|
if address, err := shim.ReadAddress("address"); err == nil {
|
||||||
|
_ = shim.RemoveSocket(address)
|
||||||
|
}
|
||||||
return empty, nil
|
return empty, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@ -105,6 +104,10 @@ func New(ctx context.Context, id string, publisher shim.Publisher, shutdown func
|
|||||||
return nil, errors.Wrap(err, "failed to initialized platform behavior")
|
return nil, errors.Wrap(err, "failed to initialized platform behavior")
|
||||||
}
|
}
|
||||||
go s.forward(ctx, publisher)
|
go s.forward(ctx, publisher)
|
||||||
|
|
||||||
|
if address, err := shim.ReadAddress("address"); err == nil {
|
||||||
|
s.shimAddress = address
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +127,8 @@ type service struct {
|
|||||||
|
|
||||||
containers map[string]*runc.Container
|
containers map[string]*runc.Container
|
||||||
|
|
||||||
cancel func()
|
shimAddress string
|
||||||
|
cancel func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommand(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (*exec.Cmd, error) {
|
func newCommand(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (*exec.Cmd, error) {
|
||||||
@ -183,30 +187,48 @@ func (s *service) StartShim(ctx context.Context, id, containerdBinary, container
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
address, err := shim.SocketAddress(ctx, grouping)
|
address, err := shim.SocketAddress(ctx, containerdAddress, grouping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
socket, err := shim.NewSocket(address)
|
socket, err := shim.NewSocket(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "address already in use") {
|
// the only time where this would happen is if there is a bug and the socket
|
||||||
|
// was not cleaned up in the cleanup method of the shim or we are using the
|
||||||
|
// grouping functionality where the new process should be run with the same
|
||||||
|
// shim as an existing container
|
||||||
|
if !shim.SocketEaddrinuse(err) {
|
||||||
|
return "", errors.Wrap(err, "create new shim socket")
|
||||||
|
}
|
||||||
|
if shim.CanConnect(address) {
|
||||||
if err := shim.WriteAddress("address", address); err != nil {
|
if err := shim.WriteAddress("address", address); err != nil {
|
||||||
return "", err
|
return "", errors.Wrap(err, "write existing socket for shim")
|
||||||
}
|
}
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
return "", err
|
if err := shim.RemoveSocket(address); err != nil {
|
||||||
|
return "", errors.Wrap(err, "remove pre-existing socket")
|
||||||
|
}
|
||||||
|
if socket, err = shim.NewSocket(address); err != nil {
|
||||||
|
return "", errors.Wrap(err, "try create new shim socket 2x")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer socket.Close()
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
socket.Close()
|
||||||
|
_ = shim.RemoveSocket(address)
|
||||||
|
}
|
||||||
|
}()
|
||||||
f, err := socket.File()
|
f, err := socket.File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
|
f.Close()
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -273,7 +295,6 @@ func (s *service) Cleanup(ctx context.Context) (*taskAPI.DeleteResponse, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime, err := runc.ReadRuntime(path)
|
runtime, err := runc.ReadRuntime(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -652,7 +673,9 @@ func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*pt
|
|||||||
if s.platform != nil {
|
if s.platform != nil {
|
||||||
s.platform.Close()
|
s.platform.Close()
|
||||||
}
|
}
|
||||||
|
if s.shimAddress != "" {
|
||||||
|
_ = shim.RemoveSocket(s.shimAddress)
|
||||||
|
}
|
||||||
return empty, nil
|
return empty, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ func parseFlags() {
|
|||||||
flag.BoolVar(&versionFlag, "v", false, "show the shim version and exit")
|
flag.BoolVar(&versionFlag, "v", false, "show the shim version and exit")
|
||||||
flag.StringVar(&namespaceFlag, "namespace", "", "namespace that owns the shim")
|
flag.StringVar(&namespaceFlag, "namespace", "", "namespace that owns the shim")
|
||||||
flag.StringVar(&idFlag, "id", "", "id of the task")
|
flag.StringVar(&idFlag, "id", "", "id of the task")
|
||||||
flag.StringVar(&socketFlag, "socket", "", "abstract socket path to serve")
|
flag.StringVar(&socketFlag, "socket", "", "socket path to serve")
|
||||||
flag.StringVar(&bundlePath, "bundle", "", "path to the bundle if not workdir")
|
flag.StringVar(&bundlePath, "bundle", "", "path to the bundle if not workdir")
|
||||||
|
|
||||||
flag.StringVar(&addressFlag, "address", "", "grpc address back to main containerd")
|
flag.StringVar(&addressFlag, "address", "", "grpc address back to main containerd")
|
||||||
@ -195,7 +195,6 @@ func run(id string, initFunc Init, config Config) error {
|
|||||||
ctx = context.WithValue(ctx, OptsKey{}, Opts{BundlePath: bundlePath, Debug: debugFlag})
|
ctx = context.WithValue(ctx, OptsKey{}, Opts{BundlePath: bundlePath, Debug: debugFlag})
|
||||||
ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", id))
|
ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", id))
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
service, err := initFunc(ctx, idFlag, publisher, cancel)
|
service, err := initFunc(ctx, idFlag, publisher, cancel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -300,11 +299,15 @@ func serve(ctx context.Context, server *ttrpc.Server, path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
defer l.Close()
|
|
||||||
if err := server.Serve(ctx, l); err != nil &&
|
if err := server.Serve(ctx, l); err != nil &&
|
||||||
!strings.Contains(err.Error(), "use of closed network connection") {
|
!strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure")
|
logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure")
|
||||||
}
|
}
|
||||||
|
l.Close()
|
||||||
|
if address, err := ReadAddress("address"); err == nil {
|
||||||
|
_ = RemoveSocket(address)
|
||||||
|
}
|
||||||
|
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,15 @@ func serveListener(path string) (net.Listener, error) {
|
|||||||
l, err = net.FileListener(os.NewFile(3, "socket"))
|
l, err = net.FileListener(os.NewFile(3, "socket"))
|
||||||
path = "[inherited from parent]"
|
path = "[inherited from parent]"
|
||||||
} else {
|
} else {
|
||||||
if len(path) > 106 {
|
if len(path) > socketPathLimit {
|
||||||
return nil, errors.Errorf("%q: unix socket path too long (> 106)", path)
|
return nil, errors.Errorf("%q: unix socket path too long (> %d)", path, socketPathLimit)
|
||||||
}
|
}
|
||||||
l, err = net.Listen("unix", "\x00"+path)
|
l, err = net.Listen("unix", path)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logrus.WithField("socket", path).Debug("serving api on abstract socket")
|
logrus.WithField("socket", path).Debug("serving api on socket")
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ func WriteAddress(path, address string) error {
|
|||||||
// ErrNoAddress is returned when the address file has no content
|
// ErrNoAddress is returned when the address file has no content
|
||||||
var ErrNoAddress = errors.New("no shim address")
|
var ErrNoAddress = errors.New("no shim address")
|
||||||
|
|
||||||
// ReadAddress returns the shim's abstract socket address from the path
|
// ReadAddress returns the shim's socket address from the path
|
||||||
func ReadAddress(path string) (string, error) {
|
func ReadAddress(path string) (string, error) {
|
||||||
path, err := filepath.Abs(path)
|
path, err := filepath.Abs(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -35,7 +35,10 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const shimBinaryFormat = "containerd-shim-%s-%s"
|
const (
|
||||||
|
shimBinaryFormat = "containerd-shim-%s-%s"
|
||||||
|
socketPathLimit = 106
|
||||||
|
)
|
||||||
|
|
||||||
func getSysProcAttr() *syscall.SysProcAttr {
|
func getSysProcAttr() *syscall.SysProcAttr {
|
||||||
return &syscall.SysProcAttr{
|
return &syscall.SysProcAttr{
|
||||||
@ -63,20 +66,21 @@ func AdjustOOMScore(pid int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SocketAddress returns an abstract socket address
|
const socketRoot = "/run/containerd"
|
||||||
func SocketAddress(ctx context.Context, id string) (string, error) {
|
|
||||||
|
// SocketAddress returns a socket address
|
||||||
|
func SocketAddress(ctx context.Context, socketPath, id string) (string, error) {
|
||||||
ns, err := namespaces.NamespaceRequired(ctx)
|
ns, err := namespaces.NamespaceRequired(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
d := sha256.Sum256([]byte(filepath.Join(ns, id)))
|
d := sha256.Sum256([]byte(filepath.Join(socketPath, ns, id)))
|
||||||
return filepath.Join(string(filepath.Separator), "containerd-shim", fmt.Sprintf("%x.sock", d)), nil
|
return fmt.Sprintf("unix://%s/%x", filepath.Join(socketRoot, "s"), d), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnonDialer returns a dialer for an abstract socket
|
// AnonDialer returns a dialer for a socket
|
||||||
func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
|
func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
|
||||||
address = strings.TrimPrefix(address, "unix://")
|
return dialer.Dialer(socket(address).path(), timeout)
|
||||||
return dialer.Dialer("\x00"+address, timeout)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnonReconnectDialer(address string, timeout time.Duration) (net.Conn, error) {
|
func AnonReconnectDialer(address string, timeout time.Duration) (net.Conn, error) {
|
||||||
@ -85,12 +89,82 @@ func AnonReconnectDialer(address string, timeout time.Duration) (net.Conn, error
|
|||||||
|
|
||||||
// NewSocket returns a new socket
|
// NewSocket returns a new socket
|
||||||
func NewSocket(address string) (*net.UnixListener, error) {
|
func NewSocket(address string) (*net.UnixListener, error) {
|
||||||
if len(address) > 106 {
|
var (
|
||||||
return nil, errors.Errorf("%q: unix socket path too long (> 106)", address)
|
sock = socket(address)
|
||||||
|
path = sock.path()
|
||||||
|
)
|
||||||
|
if !sock.isAbstract() {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0600); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "%s", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
l, err := net.Listen("unix", "\x00"+address)
|
l, err := net.Listen("unix", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to listen to abstract unix socket %q", address)
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := os.Chmod(path, 0600); err != nil {
|
||||||
|
os.Remove(sock.path())
|
||||||
|
l.Close()
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return l.(*net.UnixListener), nil
|
return l.(*net.UnixListener), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const abstractSocketPrefix = "\x00"
|
||||||
|
|
||||||
|
type socket string
|
||||||
|
|
||||||
|
func (s socket) isAbstract() bool {
|
||||||
|
return !strings.HasPrefix(string(s), "unix://")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s socket) path() string {
|
||||||
|
path := strings.TrimPrefix(string(s), "unix://")
|
||||||
|
// if there was no trim performed, we assume an abstract socket
|
||||||
|
if len(path) == len(s) {
|
||||||
|
path = abstractSocketPrefix + path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSocket removes the socket at the specified address if
|
||||||
|
// it exists on the filesystem
|
||||||
|
func RemoveSocket(address string) error {
|
||||||
|
sock := socket(address)
|
||||||
|
if !sock.isAbstract() {
|
||||||
|
return os.Remove(sock.path())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SocketEaddrinuse returns true if the provided error is caused by the
|
||||||
|
// EADDRINUSE error number
|
||||||
|
func SocketEaddrinuse(err error) bool {
|
||||||
|
netErr, ok := err.(*net.OpError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if netErr.Op != "listen" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
syscallErr, ok := netErr.Err.(*os.SyscallError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errno, ok := syscallErr.Err.(syscall.Errno)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return errno == syscall.EADDRINUSE
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanConnect returns true if the socket provided at the address
|
||||||
|
// is accepting new connections
|
||||||
|
func CanConnect(address string) bool {
|
||||||
|
conn, err := AnonDialer(address, 100*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -79,3 +79,9 @@ func AnonDialer(address string, timeout time.Duration) (net.Conn, error) {
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveSocket removes the socket at the specified address if
|
||||||
|
// it exists on the filesystem
|
||||||
|
func RemoveSocket(address string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user