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

[WIP] Introduce unix socket proxying for Docker API clients #11643

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion cmd/podman/machine/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (
initOpts = machine.InitOptions{}
defaultMachineName = "podman-machine-default"
now bool
nocompat bool
)

func init() {
Expand Down Expand Up @@ -70,6 +71,18 @@ func init() {
"Start machine now",
)

flags.BoolVar(
&nocompat,
"nocompat", false,
"Disable compatibility socket proxies when starting",
)

flags.BoolVar(
&initOpts.NoLink,
"nolink", false,
"Do not link /var/run/docker.sock",
)

ImagePathFlagName := "image-path"
flags.StringVar(&initOpts.ImagePath, ImagePathFlagName, cfg.Engine.MachineImage, "Path to qcow image")
_ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault)
Expand Down Expand Up @@ -105,7 +118,7 @@ func initMachine(cmd *cobra.Command, args []string) error {
return err
}
if now {
err = vm.Start(initOpts.Name, machine.StartOptions{})
err = vm.Start(initOpts.Name, machine.StartOptions{NoCompat: nocompat, NoLink: initOpts.NoLink})
if err == nil {
fmt.Printf("Machine %q started successfully\n", initOpts.Name)
}
Expand Down
12 changes: 7 additions & 5 deletions cmd/podman/machine/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
package machine

import (
"fmt"

"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/machine"
"github.com/containers/podman/v3/pkg/machine/qemu"
Expand All @@ -14,21 +12,26 @@ import (

var (
startCmd = &cobra.Command{
Use: "start [MACHINE]",
Use: "start [options] [MACHINE]",
Short: "Start an existing machine",
Long: "Start a managed virtual machine ",
RunE: start,
Args: cobra.MaximumNArgs(1),
Example: `podman machine start myvm`,
ValidArgsFunction: autocompleteMachine,
}

startOptions machine.StartOptions
)

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: startCmd,
Parent: machineCmd,
})
flags := startCmd.Flags()
flags.BoolVar(&startOptions.NoCompat, "nocompat", false, "Disable Docker API compatibility socket proxies")
flags.BoolVar(&startOptions.NoLink, "nolink", false, "Do not link /var/run/docker.sock",)
}

func start(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -60,9 +63,8 @@ func start(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if err := vm.Start(vmName, machine.StartOptions{}); err != nil {
if err := vm.Start(vmName, startOptions); err != nil {
return err
}
fmt.Printf("Machine %q started successfully\n", vmName)
return nil
}
7 changes: 7 additions & 0 deletions cmd/podman/system/umask_noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build windows

package system

func umask(mask int) int {
return 0
}
9 changes: 9 additions & 0 deletions cmd/podman/system/umask_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// +build !windows

package system

import "syscall"

func umask(mask int) int {
return syscall.Umask(mask)
}
249 changes: 249 additions & 0 deletions cmd/podman/system/unixproxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package system

import (
"fmt"
"io"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
"time"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/common"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
)

var (
unixProxyDescription = `Proxy a remote podman service

Proxies a remote podman service over a local unix domain socket.
`

upCmd = &cobra.Command{
Use: "unix-proxy [options] [URI]",
Args: cobra.MaximumNArgs(1),
Short: "Proxies a remote podman service over a local unix domain socket",
Long: unixProxyDescription,
RunE: proxy,
ValidArgsFunction: common.AutocompleteDefaultOneArg,
Example: `podman system unix-proxy unix:///tmp/podman.sock`,
}

upArgs = struct {
PidFile string
Quiet bool
}{}
)

type CloseWriteStream interface {
io.Reader
io.WriteCloser
CloseWrite() error
}

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: upCmd,
Parent: systemCmd,
})

flags := upCmd.Flags()

flags.StringVarP(&upArgs.PidFile, "pid-file", "", "", "File to save PID")
_ = upCmd.RegisterFlagCompletionFunc("pid-file", completion.AutocompleteNone)
flags.BoolVarP(&upArgs.Quiet, "quiet", "q", false, "Suppress printed output")
_ = upCmd.RegisterFlagCompletionFunc("quiet", completion.AutocompleteNone)
}

func proxy(cmd *cobra.Command, args []string) error {
apiURI, err := resolveUnixURI(args)
if err != nil {
return err
}
logrus.Infof("using API endpoint: '%s'", apiURI)
// Clean up any old existing unix domain socket
var uri *url.URL
if len(apiURI) > 0 {
var err error
uri, err = url.Parse(apiURI)
if err != nil {
return err
}

// socket activation uses a unix:// socket in the shipped unit files but apiURI is coded as "" at this layer.
if uri.Scheme == "unix" {
if err := os.Remove(uri.Path); err != nil && !os.IsNotExist(err) {
return err
}
} else {
return errors.Errorf("Only unix domain sockets are supported as a proxy address: %s", uri)
}
}

if len(upArgs.PidFile) > 0 {
f, err := os.Create(upArgs.PidFile)
if err != nil {
errors.Wrap(err, "Error creating pid")
}
defer os.Remove(upArgs.PidFile)
pid := os.Getpid()
if _, err := f.WriteString(strconv.Itoa(pid)); err != nil {
errors.Wrap(err, "Error creating pid")
}
}

return setupProxy(uri)
}

func connectForward(bastion *bindings.Bastion) (CloseWriteStream, error) {
for retries := 1; ; retries++ {
forward, err := bastion.Client.Dial("unix", bastion.URI.Path)
if err == nil {
return forward.(ssh.Channel), nil
}
// Check if ssh connection is still alive
_, _, err2 := bastion.Client.Conn.SendRequest("alive@podman", true, nil)
if err2 != nil || retries > 2 {
// couldn't reconnect ssh tunnel, or the destination is unreachable
return nil, errors.Wrapf(err, "Couldn't reestablish ssh connection: %s", bastion.URI)
}

bastion.Reconnect()
}
}

func listenUnix(socketURI *url.URL) (net.Listener, error) {
oldmask := umask(0177)
defer umask(oldmask)
listener, err := net.Listen("unix", socketURI.Path)
if err != nil {
return listener, errors.Wrapf(err, "Error listening on socket: %s", socketURI.Path)
}

return listener, nil
}

func setupProxy(socketURI *url.URL) error {
cfg := registry.PodmanConfig()

uri, err := url.Parse(cfg.URI)
if err != nil {
return errors.Wrapf(err, "Not a valid url: %s", uri)
}

if uri.Scheme != "ssh" {
return errors.Errorf("Only ssh is supported, specify another connection: %s", uri)
}

bastion, err := bindings.CreateBastion(uri, "", cfg.Identity)
defer bastion.Client.Close()
if err != nil {
return err
}

printfOrQuiet("SSH Bastion connected: %s\n", uri)

listener, err := listenUnix(socketURI)
if err != nil {
return errors.Wrapf(err, "Error listening on socket: %s", socketURI.Path)
}
defer listener.Close()

printfOrQuiet("Listening on: %s\n", socketURI)

for {
acceptConnection(listener, &bastion, socketURI)
}
}

func printfOrQuiet(format string, a ...interface{}) (n int, err error) {
if !upArgs.Quiet {
return fmt.Printf(format, a...)
}

return 0, nil
}

func acceptConnection(listener net.Listener, bastion *bindings.Bastion, socketURI *url.URL) error {
con, err := accept(listener)
if err != nil {
return errors.Wrapf(err, "Error accepting on socket: %s", socketURI.Path)
}

src, ok := con.(CloseWriteStream)
if !ok {
con.Close()
return errors.Wrapf(err, "Underlying socket does not support half-close %s", socketURI.Path)
}

dest, err := connectForward(bastion)
if err != nil {
con.Close()
logrus.Error(err)
return nil // eat
}

go forward(src, dest)
go forward(dest, src)

return nil
}

func backOff(delay time.Duration) time.Duration {
if delay == 0 {
delay = 5 * time.Millisecond
} else {
delay *= 2
}
if delay > time.Second {
delay = time.Second
}
return delay
}

func accept(listener net.Listener) (net.Conn, error) {
con, err := listener.Accept()
delay := time.Duration(0)
if ne, ok := err.(net.Error); ok && ne.Temporary() {
delay = backOff(delay)
time.Sleep(delay)
}
return con, err
}

func forward(src io.Reader, dest CloseWriteStream) {
defer dest.CloseWrite()
io.Copy(dest, src)
}

func resolveUnixURI(_url []string) (string, error) {
socketName := "podman-remote.sock"

if len(_url) > 0 && _url[0] != "" {
return _url[0], nil
}

xdg, err := util.GetRuntimeDir()
if rootless.IsRootless() {
xdg = os.TempDir()
}

if err != nil {
return "", err
}

socketPath := filepath.Join(xdg, "podman", socketName)
if err := os.MkdirAll(filepath.Dir(socketPath), 0700); err != nil {
return "", err
}
return "unix:" + socketPath, nil
}
Loading