podman unshare: add --rootless-cni to join the ns

Add a new --rootless-cni option to podman unshare to also join the
rootless-cni network namespace. This is useful if you want to connect
to a rootless container via IP address. This is only possible from the
rootless-cni namespace and not from the host namespace. This option also
helps to debug problems in the rootless-cni namespace.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger
2021-03-29 18:57:54 +02:00
parent 0e67053b9a
commit 0a39ad196c
8 changed files with 90 additions and 22 deletions

View File

@ -12,9 +12,10 @@ import (
) )
var ( var (
unshareOptions = entities.SystemUnshareOptions{}
unshareDescription = "Runs a command in a modified user namespace." unshareDescription = "Runs a command in a modified user namespace."
unshareCommand = &cobra.Command{ unshareCommand = &cobra.Command{
Use: "unshare [COMMAND [ARG...]]", Use: "unshare [options] [COMMAND [ARG...]]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: "Run a command in a modified user namespace", Short: "Run a command in a modified user namespace",
Long: unshareDescription, Long: unshareDescription,
@ -33,6 +34,7 @@ func init() {
}) })
flags := unshareCommand.Flags() flags := unshareCommand.Flags()
flags.SetInterspersed(false) flags.SetInterspersed(false)
flags.BoolVar(&unshareOptions.RootlessCNI, "rootless-cni", false, "Join the rootless network namespace used for CNI networking")
} }
func unshare(cmd *cobra.Command, args []string) error { func unshare(cmd *cobra.Command, args []string) error {
@ -49,5 +51,5 @@ func unshare(cmd *cobra.Command, args []string) error {
args = []string{shell} args = []string{shell}
} }
return registry.ContainerEngine().Unshare(registry.Context(), args) return registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
} }

View File

@ -24,6 +24,19 @@ The unshare session defines two environment variables:
- **CONTAINERS_GRAPHROOT**: the path to the persistent container's data. - **CONTAINERS_GRAPHROOT**: the path to the persistent container's data.
- **CONTAINERS_RUNROOT**: the path to the volatile container's data. - **CONTAINERS_RUNROOT**: the path to the volatile container's data.
## OPTIONS
#### **\-\-help**, **-h**
Print usage statement
#### **\-\-rootless-cni**
Join the rootless network namespace used for CNI networking. It can be used to
connect to a rootless container via IP address (CNI networking). This is otherwise
not possible from the host network namespace.
_Note: Using this option with more than one unshare session can have unexpected results._
## EXAMPLE ## EXAMPLE
``` ```
@ -35,6 +48,30 @@ $ podman unshare cat /proc/self/uid_map /proc/self/gid_map
1 10000 65536 1 10000 65536
0 1000 1 0 1000 1
1 10000 65536 1 10000 65536
$ podman unshare --rootless-cni ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 36:0e:4a:c7:45:7e brd ff:ff:ff:ff:ff:ff
inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
valid_lft forever preferred_lft forever
inet6 fe80::340e:4aff:fec7:457e/64 scope link
valid_lft forever preferred_lft forever
3: cni-podman2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 5e:3a:71:d2:b4:3a brd ff:ff:ff:ff:ff:ff
inet 10.89.1.1/24 brd 10.89.1.255 scope global cni-podman2
valid_lft forever preferred_lft forever
inet6 fe80::5c3a:71ff:fed2:b43a/64 scope link
valid_lft forever preferred_lft forever
4: vethd4ba3a2f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman2 state UP group default
link/ether 8a:c9:56:32:17:0c brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::88c9:56ff:fe32:170c/64 scope link
valid_lft forever preferred_lft forever
``` ```

View File

@ -105,13 +105,13 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
return ctrNetwork return ctrNetwork
} }
type rootlessCNI struct { type RootlessCNI struct {
ns ns.NetNS ns ns.NetNS
dir string dir string
lock lockfile.Locker lock lockfile.Locker
} }
func (r *rootlessCNI) Do(toRun func() error) error { func (r *RootlessCNI) Do(toRun func() error) error {
err := r.ns.Do(func(_ ns.NetNS) error { err := r.ns.Do(func(_ ns.NetNS) error {
// before we can run the given function // before we can run the given function
// we have to setup all mounts correctly // we have to setup all mounts correctly
@ -174,9 +174,14 @@ func (r *rootlessCNI) Do(toRun func() error) error {
return err return err
} }
// cleanup the rootless cni namespace if needed // Cleanup the rootless cni namespace if needed
// check if we have running containers with the bridge network mode // check if we have running containers with the bridge network mode
func (r *rootlessCNI) cleanup(runtime *Runtime) error { func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
_, err := os.Stat(r.dir)
if os.IsNotExist(err) {
// the directory does not exists no need for cleanup
return nil
}
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
running := func(c *Container) bool { running := func(c *Container) bool {
@ -234,10 +239,10 @@ func (r *rootlessCNI) cleanup(runtime *Runtime) error {
return nil return nil
} }
// getRootlessCNINetNs returns the rootless cni object. If create is set to true // GetRootlessCNINetNs returns the rootless cni object. If create is set to true
// the rootless cni namespace will be created if it does not exists already. // the rootless cni namespace will be created if it does not exists already.
func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) { func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
var rootlessCNINS *rootlessCNI var rootlessCNINS *RootlessCNI
if rootless.IsRootless() { if rootless.IsRootless() {
runDir, err := util.GetRuntimeDir() runDir, err := util.GetRuntimeDir()
if err != nil { if err != nil {
@ -421,7 +426,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
os.Setenv("PATH", path) os.Setenv("PATH", path)
} }
rootlessCNINS = &rootlessCNI{ rootlessCNINS = &RootlessCNI{
ns: ns, ns: ns,
dir: cniDir, dir: cniDir,
lock: lock, lock: lock,
@ -433,7 +438,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
// setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni // setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni
// networks. If rootless it will join/create the rootless cni namespace. // networks. If rootless it will join/create the rootless cni namespace.
func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) { func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) {
rootlessCNINS, err := r.getRootlessCNINetNs(true) rootlessCNINS, err := r.GetRootlessCNINetNs(true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -651,7 +656,7 @@ func (r *Runtime) closeNetNS(ctr *Container) error {
// Tear down a container's CNI network configuration and joins the // Tear down a container's CNI network configuration and joins the
// rootless net ns as rootless user // rootless net ns as rootless user
func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error { func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
rootlessCNINS, err := r.getRootlessCNINetNs(false) rootlessCNINS, err := r.GetRootlessCNINetNs(false)
if err != nil { if err != nil {
return err return err
} }
@ -665,7 +670,7 @@ func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
// execute the cni setup in the rootless net ns // execute the cni setup in the rootless net ns
err = rootlessCNINS.Do(tearDownPod) err = rootlessCNINS.Do(tearDownPod)
if err == nil { if err == nil {
err = rootlessCNINS.cleanup(r) err = rootlessCNINS.Cleanup(r)
} }
} else { } else {
err = tearDownPod() err = tearDownPod()

View File

@ -88,7 +88,7 @@ type ContainerEngine interface {
SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error)
Shutdown(ctx context.Context) Shutdown(ctx context.Context)
SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
Unshare(ctx context.Context, args []string) error Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error
Version(ctx context.Context) (*SystemVersionReport, error) Version(ctx context.Context) (*SystemVersionReport, error)
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error) VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error)

View File

@ -98,6 +98,11 @@ type SystemVersionReport struct {
Server *define.Version `json:",omitempty"` Server *define.Version `json:",omitempty"`
} }
// SystemUnshareOptions describes the options for the unshare command
type SystemUnshareOptions struct {
RootlessCNI bool
}
type ComponentVersion struct { type ComponentVersion struct {
types.Version types.Version
} }

View File

@ -390,13 +390,25 @@ func unshareEnv(graphroot, runroot string) []string {
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot)) fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
} }
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
cmd := exec.Command(args[0], args[1:]...) unshare := func() error {
cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot) cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
cmd.Stdout = os.Stdout cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout
return cmd.Run() cmd.Stderr = os.Stderr
return cmd.Run()
}
if options.RootlessCNI {
rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true)
if err != nil {
return err
}
defer rootlesscni.Cleanup(ic.Libpod)
return rootlesscni.Do(unshare)
}
return unshare()
} }
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) { func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {

View File

@ -28,7 +28,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
return system.DiskUsage(ic.ClientCtx, nil) return system.DiskUsage(ic.ClientCtx, nil)
} }
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
return errors.New("unshare is not supported on remote clients") return errors.New("unshare is not supported on remote clients")
} }

View File

@ -49,4 +49,11 @@ var _ = Describe("Podman unshare", func() {
ok, _ := session.GrepString(userNS) ok, _ := session.GrepString(userNS)
Expect(ok).To(BeFalse()) Expect(ok).To(BeFalse())
}) })
It("podman unshare --rootles-cni", func() {
session := podmanTest.Podman([]string{"unshare", "--rootless-cni", "ip", "addr"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring("tap0"))
})
}) })