Skip to content

Commit

Permalink
lxd_connection: Allow non-root users to connect to an instance (#9659)
Browse files Browse the repository at this point in the history
* fix: add support for non-root user

* fix: show correct info for connection

* fix: use build_exec_command to execute as nonroot

* unset default user

* feat: add options for setting remote user and become method

* fix: add root as default remote_user

* fix: remove ansible_ssh_user from remote_user vars

* fix: use single quotes inside f-string

* fix: ensure lxc exec comes first

* fix: line length

* fix: use -c flag with su

* Update plugins/connection/lxd.py

Co-authored-by: Felix Fontein <[email protected]>

* Update plugins/connection/lxd.py

Co-authored-by: Felix Fontein <[email protected]>

* Update plugins/connection/lxd.py

Co-authored-by: Felix Fontein <[email protected]>

* doc: add changelog fragment

* fix: use underscore for module name in fragment

* Update 9659-lxd_connection-nonroot-user.yml

Co-authored-by: Felix Fontein <[email protected]>

* fix: add put command

* feat: add get_remote_uid_gid placeholder function

* feat: complete placeholder _get_remote_uid_gid function

* fix: better logging

* fix: ensure default values are of type str

* fix: use ints for uid and gid

* fix: print put command

* fix: format

* fix: display msg for PUT

* fix: add comment about defaults

* fix: format

* fix: use os module to get uid and gid

* Revert "fix: use os module to get uid and gid"

This reverts commit bb2ba14.

* Update plugins/connection/lxd.py

Co-authored-by: Felix Fontein <[email protected]>

* fix: omit uid, gid args in lxd file push if root

---------

Co-authored-by: Felix Fontein <[email protected]>
(cherry picked from commit 06df717)
  • Loading branch information
yeetypete authored and patchback[bot] committed Feb 15, 2025
1 parent fa846e9 commit f49e6a1
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 20 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/9659-lxd_connection-nonroot-user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- lxd connection plugin - adds ``remote_user`` and ``lxd_become_method`` parameters for allowing a non-root user to connect to an LXD instance (https://github.com/ansible-collections/community.general/pull/9659).
116 changes: 96 additions & 20 deletions plugins/connection/lxd.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
vars:
- name: ansible_executable
- name: ansible_lxd_executable
lxd_become_method:
description:
- Become command used to switch to a non-root user.
- Is only used when O(remote_user) is not V(root).
type: str
default: /bin/su
vars:
- name: lxd_become_method
version_added: 10.4.0
remote:
description:
- Name of the LXD remote to use.
Expand All @@ -40,6 +49,22 @@
vars:
- name: ansible_lxd_remote
version_added: 2.0.0
remote_user:
description:
- User to login/authenticate as.
- Can be set from the CLI via the C(--user) or C(-u) options.
type: string
default: root
vars:
- name: ansible_user
env:
- name: ANSIBLE_REMOTE_USER
ini:
- section: defaults
key: remote_user
keyword:
- name: remote_user
version_added: 10.4.0
project:
description:
- Name of the LXD project to use.
Expand All @@ -63,7 +88,6 @@ class Connection(ConnectionBase):

transport = 'community.general.lxd'
has_pipelining = True
default_user = 'root'

def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
Expand All @@ -73,9 +97,6 @@ def __init__(self, play_context, new_stdin, *args, **kwargs):
except ValueError:
raise AnsibleError("lxc command not found in PATH")

if self._play_context.remote_user is not None and self._play_context.remote_user != 'root':
self._display.warning('lxd does not support remote_user, using default: root')

def _host(self):
""" translate remote_addr to lxd (short) hostname """
return self.get_option("remote_addr").split(".", 1)[0]
Expand All @@ -85,25 +106,40 @@ def _connect(self):
super(Connection, self)._connect()

if not self._connected:
self._display.vvv("ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
self._display.vvv(f"ESTABLISH LXD CONNECTION FOR USER: {self.get_option('remote_user')}", host=self._host())
self._connected = True

def _build_command(self, cmd) -> str:
"""build the command to execute on the lxd host"""

exec_cmd = [self._lxc_cmd]

if self.get_option("project"):
exec_cmd.extend(["--project", self.get_option("project")])

exec_cmd.extend(["exec", f"{self.get_option('remote')}:{self._host()}", "--"])

if self.get_option("remote_user") != "root":
self._display.vvv(
f"INFO: Running as non-root user: {self.get_option('remote_user')}, \
trying to run 'lxc exec' with become method: {self.get_option('lxd_become_method')}",
host=self._host(),
)
exec_cmd.extend(
[self.get_option("lxd_become_method"), self.get_option("remote_user"), "-c"]
)

exec_cmd.extend([self.get_option("executable"), "-c", cmd])

return exec_cmd

def exec_command(self, cmd, in_data=None, sudoable=True):
""" execute a command on the lxd host """
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)

self._display.vvv(f"EXEC {cmd}", host=self._host())

local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"exec",
f"{self.get_option('remote')}:{self._host()}",
"--",
self.get_option("executable"), "-c", cmd
])

local_cmd = self._build_command(cmd)
self._display.vvvvv(f"EXEC {local_cmd}", host=self._host())

local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
Expand All @@ -125,6 +161,25 @@ def exec_command(self, cmd, in_data=None, sudoable=True):

return process.returncode, stdout, stderr

def _get_remote_uid_gid(self) -> tuple[int, int]:
"""Get the user and group ID of 'remote_user' from the instance."""

rc, uid_out, err = self.exec_command("/bin/id -u")
if rc != 0:
raise AnsibleError(
f"Failed to get remote uid for user {self.get_option('remote_user')}: {err}"
)
uid = uid_out.strip()

rc, gid_out, err = self.exec_command("/bin/id -g")
if rc != 0:
raise AnsibleError(
f"Failed to get remote gid for user {self.get_option('remote_user')}: {err}"
)
gid = gid_out.strip()

return int(uid), int(gid)

def put_file(self, in_path, out_path):
""" put a file from local to lxd """
super(Connection, self).put_file(in_path, out_path)
Expand All @@ -137,11 +192,32 @@ def put_file(self, in_path, out_path):
local_cmd = [self._lxc_cmd]
if self.get_option("project"):
local_cmd.extend(["--project", self.get_option("project")])
local_cmd.extend([
"file", "push",
in_path,
f"{self.get_option('remote')}:{self._host()}/{out_path}"
])

if self.get_option("remote_user") != "root":
uid, gid = self._get_remote_uid_gid()
local_cmd.extend(
[
"file",
"push",
"--uid",
str(uid),
"--gid",
str(gid),
in_path,
f"{self.get_option('remote')}:{self._host()}/{out_path}",
]
)
else:
local_cmd.extend(
[
"file",
"push",
in_path,
f"{self.get_option('remote')}:{self._host()}/{out_path}",
]
)

self._display.vvvvv(f"PUT {local_cmd}", host=self._host())

local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]

Expand Down

0 comments on commit f49e6a1

Please sign in to comment.