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

Support rootfull pods from inside #300

Open
0b11stan opened this issue Sep 16, 2021 · 3 comments
Open

Support rootfull pods from inside #300

0b11stan opened this issue Sep 16, 2021 · 3 comments
Labels
enhancement New feature or request

Comments

@0b11stan
Copy link

0b11stan commented Sep 16, 2021

/kind feature

This feature request is about the connection plugin documented here. To my knowledge it is not possible to join a rootfull container (container created with sudo). It would be nice to be able to do so. I propose adding an ansible_podman_rootless flag which is false by default but can be set to true to enable a connection to a "rootfull" pod. The host sudo password may be asked via the --ask-pass argument.

Steps to test the feature:

python -m venv venv
source venv/bin/activate
pip install ansible
podman run --rm --name rootless -d docker.io/python bash -c "while true; do sleep 10000; done"
sudo podman run --rm --name rootfull -d docker.io/python bash -c "while true; do sleep 10000; done"
cat > inventory.yml <<EOF
all:
  hosts:
    rootless:
      ansible_connection: podman
    rootfull:
      ansible_connection: podman
      ansible_podman_rootless: false
EOF
cat > main.yml <<EOF
---
- name: Converge
  hosts: rootless
  tasks:
    - command: 'hostname -i'
      register: hostip
    - debug: var=hostip.stdout

- name: Converge
  hosts: rootfull
  tasks:
    - command: 'hostname -i'
      register: hostip
    - debug: var=hostip.stdout
EOF
ansible-playbook -k -i inventory.yml main.yml

Describe the results you received:

PLAY [Converge] ***************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [rootless]

TASK [command] ****************************************************************************
changed: [rootless]

TASK [debug] ******************************************************************************
ok: [rootless] => {
    "hostip.stdout": "10.0.2.100"
}

PLAY [Converge] ***************************************************************************

TASK [Gathering Facts] ********************************************************************
fatal: [rootfull]: UNREACHABLE! => {"changed": false, "msg": "Failed to create temporary directory.In some cases, you may have been able to authenticate and did not have permissions on the target directory. Consider changing the remote tmp path in ansible.cfg to a path rooted in \"/tmp\", for more error information use -vvv. Failed command was: ( umask 77 && mkdir -p \"` echo ~/.ansible/tmp `\"&& mkdir \"` echo ~/.ansible/tmp/ansible-tmp-1631805229.2738547-283320-190633098617757 `\" && echo ansible-tmp-1631805229.2738547-283320-190633098617757=\"` echo ~/.ansible/tmp/ansible-tmp-1631805229.2738547-283320-190633098617757 `\" ), exited with result 125", "unreachable": true}

PLAY RECAP ********************************************************************************
rootfull                   : ok=0    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0
rootless                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Describe the results you expected:

PLAY [Converge] ***************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [rootless]

TASK [command] ****************************************************************************
changed: [rootless]

TASK [debug] ******************************************************************************
ok: [rootless] => {
    "hostip.stdout": "10.0.2.100"
}

PLAY [Converge] ***************************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [rootfull]

TASK [command] ****************************************************************************
changed: [rootfull]

TASK [debug] ******************************************************************************
ok: [rootfull] => {
    "hostip.stdout": "10.88.0.14"
}

PLAY RECAP ********************************************************************************
rootfull                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
rootless                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Additional information you deem important:

This feature request is following the issue ansible-community/molecule-plugins#82.

@sshnaidm
Copy link
Member

TBH I don't think it's possible to inject sudo password into rootless connection, but I'll take a look.

@sshnaidm sshnaidm added the enhancement New feature or request label Sep 17, 2021
@0b11stan
Copy link
Author

0b11stan commented Sep 20, 2021

Well, I am trying something inspired from the state machine in ssh connection
It's maybe a weird solution but I'm getting closer.

@0b11stan
Copy link
Author

0b11stan commented Sep 20, 2021

I have the following patch as a proof of concept. If you think that it's not a too ugly solution, i'll fix my code a bit, add some tests and propose a PR.

From c42e459932f49a65e66aa23f19d77ff52e4f3624 Mon Sep 17 00:00:00 2001
From: Tristan Pinaudeau <[email protected]>
Date: Tue, 21 Sep 2021 18:07:15 +0200
Subject: [PATCH 1/2] feat: enable rootfull container access

---
 plugins/connection/podman.py | 59 ++++++++++++++++++++++++++++++++++--
 1 file changed, 57 insertions(+), 2 deletions(-)

diff --git a/plugins/connection/podman.py b/plugins/connection/podman.py
index 68c677f..f516988 100644
--- a/plugins/connection/podman.py
+++ b/plugins/connection/podman.py
@@ -15,8 +15,11 @@ import os
 import shlex
 import shutil
 import subprocess
+import fcntl
+import json

 from ansible.errors import AnsibleError
+from ansible.module_utils.compat import selectors
 from ansible.module_utils._text import to_bytes, to_native
 from ansible.plugins.connection import ConnectionBase, ensure_connect
 from ansible.utils.display import Display
@@ -68,6 +71,21 @@ DOCUMENTATION = '''
           - name: ansible_podman_executable
         env:
           - name: ANSIBLE_PODMAN_EXECUTABLE
+      podman_rootless:
+        description:
+          - Is the target container rootless ?
+        type: bool
+        default: true
+        vars:
+          - name: ansible_podman_rootless
+        env:
+          - name: ANSIBLE_PODMAN_ROOTLESS
+      password:
+        description: Authentication password for the C(remote_user). Can be supplied as CLI option.
+        vars:
+          - name: ansible_password
+          - name: ansible_podman_password
+          - name: ansible_podman_pass
 '''


@@ -92,6 +110,35 @@ class Connection(ConnectionBase):
         self._mount_point = None
         self.user = self._play_context.remote_user
         display.vvvv("Using podman connection from collection")
+        self._rootless = self.get_option('podman_rootless')
+
+    def _become(self, p):
+        b_stderr = b''
+
+        timeout = 2 # TODO self.get_option('timeout')
+        for fd in (p.stdout, p.stderr):
+            fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
+
+        selector = selectors.DefaultSelector()
+        selector.register(p.stdout, selectors.EVENT_READ)
+        selector.register(p.stderr, selectors.EVENT_READ)
+
+        try:
+            poll = p.poll()
+            events = selector.select(timeout)
+
+            for key, event in events:
+                if key.fileobj == p.stderr:
+                    b_chunk = p.stderr.read()
+                    b_stderr += b_chunk
+
+            if b'sudo' in b_stderr:
+                become_pass = self.get_option('password')
+                p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
+                p.stdin.flush()
+
+        finally:
+            selector.close()

     def _podman(self, cmd, cmd_args=None, in_data=None, use_container_id=True):
         """
@@ -106,7 +153,10 @@ class Connection(ConnectionBase):
         podman_cmd = distutils.spawn.find_executable(podman_exec)
         if not podman_cmd:
             raise AnsibleError("%s command not found in PATH" % podman_exec)
-        local_cmd = [podman_cmd]
+
+        # TODO find full sudo path (or use the become mechanism from ansible)
+        local_cmd = [podman_cmd] if self._rootless else ['sudo', '-Sk', podman_cmd]
+
         if self.get_option('podman_extra_args'):
             local_cmd += shlex.split(
                 to_native(
@@ -127,6 +177,9 @@ class Connection(ConnectionBase):
         p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)

+        if not self._rootless:
+            self._become(p)
+
         stdout, stderr = p.communicate(input=in_data)
         display.vvvvv("STDOUT %s" % stdout)
         display.vvvvv("STDERR %s" % stderr)
@@ -142,7 +195,9 @@ class Connection(ConnectionBase):
         """
         super(Connection, self)._connect()
         rc, self._mount_point, stderr = self._podman("mount")
-        if rc != 0:
+        if not self._rootless:
+            self._mount_point = None
+        elif rc != 0:
             display.vvvv("Failed to mount container %s: %s" % (self._container_id, stderr.strip()))
         elif not os.listdir(self._mount_point.strip()):
             display.vvvv("Failed to mount container with CGroups2: empty dir %s" % self._mount_point.strip())
--
2.33.0


From d22b1dc94b454a3ddde015b1d428e4c45763c21f Mon Sep 17 00:00:00 2001
From: Tristan Pinaudeau <[email protected]>
Date: Tue, 21 Sep 2021 18:13:06 +0200
Subject: [PATCH 2/2] fix: json lib not needed'

---
 plugins/connection/podman.py | 1 -
 1 file changed, 1 deletion(-)

diff --git a/plugins/connection/podman.py b/plugins/connection/podman.py
index f516988..555e28f 100644
--- a/plugins/connection/podman.py
+++ b/plugins/connection/podman.py
@@ -16,7 +16,6 @@ import shlex
 import shutil
 import subprocess
 import fcntl
-import json

 from ansible.errors import AnsibleError
 from ansible.module_utils.compat import selectors
--
2.33.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants