Ansible cannot find passlib when creating a user through Github Action

Hi Folks,

I am stuck and need help with my ansible playbook. I created a playbook that will configure a vm image for me on AWS, QEMU, or VMware, and the playbook is triggered by vagrant or packer. When I run the playbook locally, it works fine to create/configure the images. I wanted to automate the system further by having github actions run packer and create my images on AWS. This is failing with the following:

FAILED! => {"msg": "Unable to encrypt nor hash%!(PACKER_COMMA) passlib must be installed. No module named 'passlib'. Unable to encrypt nor hash%!(PACKER_COMMA) passlib must be installed. No module named 'passlib'"}

I’ve tried various things to fix this with no luck. I added this to me github runner:

          python3 -m pip show passlib
          ansible-config dump | grep DEFAULT_MODULE_PATH
          ansible --version

And this is what it returns:

Name: passlib
Version: 1.7.4
Summary: comprehensive password hashing framework supporting over 30 schemes
Home-page: https://passlib.readthedocs.io
Author: Eli Collins
Author-email: elic@assurancetechnologies.com
License: BSD
Location: /usr/lib/python3/dist-packages
Requires: 
Required-by: 
DEFAULT_MODULE_PATH(default) = ['/home/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible [core 2.17.2]
  config file = None
  configured module search path = ['/home/runner/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/pipx/venvs/ansible-core/lib/python3.10/site-packages/ansible
  ansible collection location = /home/runner/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/pipx_bin/ansible
  python version = 3.10.12 (main, Jul 29 2024, 16:56:48) [GCC 11.4.0] (/opt/pipx/venvs/ansible-core/bin/python)
  jinja version = 3.1.4
  libyaml = True

And finally, this is what I use to create the user/pass:

- name: create new user with password
  user:
    name: "{{ item }}"
    password: "{{ user_pw | password_hash('sha512')}}"
    groups: "adm"
    expires: "{{ '%s' | strftime( (ansible_date_time.epoch | int) + (86400 * 356)  ) }}"
    password_expire_max: 356
    password_expire_min: 356
    shell: /bin/bash

Any help would be greatly appreciated.

There can be multiple pythons, make sure the one executing Ansible is the same one that has passlib installed

On one side you are showing python3 but Ansible is specifically using python 3.10 from a venv
(/opt/pipx/venvs/ansible-core/bin/python), those might not be the same.

1 Like

Thanks Brian.

I install python3 + passlib from within the playbook and try to set the interpreter for the playbook. See the following:

- name: Install Python3
  apt:
    pkg:
      - python3
      - python3-pip
      - python3-passlib
  become: true
  environment:
    DEBIAN_FRONTEND: noninteractive

- name: Create a symbolic link from python3 to python
  file:
    src: /usr/bin/python3
    dest: /usr/bin/python
    state: link

- name: Set ansible_python_interpreter to use the installed Python
  set_fact:
    ansible_python_interpreter: /usr/bin/python3

I’m at a loss on how to get ansible to use the right python version

Setting the interpreter ONLY affects module execution, not any other plugin as they always use the interpreter Ansible itself was installed under.

passowrd_hash is a lookup plugin, which runs in Jinja, executed inside Ansible, not a module, so setting the interpreter won’t affect it.

Ansible is using the ‘right’ Python version as it is the version you installed it with, in this case a venv, using apt will install libraries in the system’s Python. You have 2 options, install the packages in the venv OR reconfigure the venv to use system packages also.

1 Like

I install passlib and dependencies when using GHA like this: ansible-role-hardening/.github/workflows/molecule.yml at c62d9d4254cdf0dffdc63c28354a6a7c861199ed · konstruktoid/ansible-role-hardening · GitHub

      - name: Install system dependencies
        run: |
          sudo apt-get update
          sudo apt-get --assume-yes --no-install-recommends install python3-pip
          python3 -m pip install --user -U ansible ansible-lint \
            jmespath molecule-plugins[docker] passlib
          python3 -m pip install --user 'requests==2.28.1'

Apologies for reviving this thread but I have been having the same issues of trying to get ansible to use passlib on the client machine.

Host Machine

07:57:29 in ansible ➜ ansible --version
ansible [core 2.17.5]
  config file = None
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/user/.local/lib/python3.12/site-packages/ansible
  ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/user/.local/bin/ansible
  python version = 3.12.3 (main, Sep 11 2024, 14:17:37) [GCC 13.2.0] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

Client Machine

Client Machine Details

root@instance:/# cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=24.04
DISTRIB_CODENAME=noble
DISTRIB_DESCRIPTION="Ubuntu 24.04.1 LTS"
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
root@instance:/# python3 -V
Python 3.12.3

Installed passlib using apt and pip

root@instance:/# apt install python3-passlib
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
python3-passlib is already the newest version (1.7.4-4).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
root@instance:/# dpkg -l| grep passlib
ii  python3-passlib               1.7.4-4                           all          comprehensive password hashing framework
root@instance:/# pip3 install passlib
Requirement already satisfied: passlib in /usr/lib/python3/dist-packages (1.7.4)
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
root@instance:/# pip3 show passlib
Name: passlib
Version: 1.7.4
Summary: comprehensive password hashing framework supporting over 30 schemes
Home-page: https://passlib.readthedocs.io
Author: Eli Collins
Author-email: elic@assurancetechnologies.com
License: BSD
Location: /usr/lib/python3/dist-packages
Requires:
Required-by:
root@instance:/# which python3
/usr/bin/python3

I have verified that when running ansible it uses the /usr/bin/python3 in the client machine

I have also tried install installing passlib using ansible tasks with both the apt module and pip module to no avail.

Also debugged to check if ansible can see the module

TASK [Debug passlib installation output] ***************************************
task path: /mnt/c/Users/User/Documents/ansible/ansible-bootstrap/molecule/adduser/prepare.yml:20
ok: [instance] => {
    "verify_passlib.stdout_lines": [
        "Name: passlib",
        "Version: 1.7.4",
        "Summary: comprehensive password hashing framework supporting over 30 schemes",
        "Home-page: https://passlib.readthedocs.io",
        "Author: Eli Collins",
        "Author-email: elic@assurancetechnologies.com",
        "License: BSD",
        "Location: /usr/lib/python3/dist-packages",
        "Requires: ",
        "Required-by: "
    ]
}  

Despite all this I still get the error that passlib is not found

TASK [darsh12.bootstrap : Add the user to the system] **************************
task path: /mnt/c/Users/User/Documents/ansible/ansible-bootstrap/tasks/adduser.yml:20
fatal: [instance]: FAILED! => {
    "msg": "Unable to encrypt nor hash, passlib must be installed. No module named 'passlib'. Unable to encrypt nor hash, passlib must be installed. No module named 'passlib'"
}

User Add Task

- name: Add the user to the system
  ansible.builtin.user:
    name: "{{ new_username }}"
    shell: /bin/bash
    groups: sudo
    append: yes
    create_home: yes
    uid: "{{ user_uid | default(omit) }}"
    password: "{{ user_password | password_hash('sha512') }}"
  when: new_username is defined
  become: yes

I also followed the advise above to use the run module to invoke python3 but still nothing.

You should not mix python packages from apt and from the system’s pip.
The warning below is pretty explicit about that.
Reinstantiate the VM and use one strategy only. Probably apt is the easiest in this case.

Dick

Hi,
Thank you for that. I have reinitialized the vm and installed passlib using apt.

root@instance:/# dpkg -l | grep passlib
root@instance:/# pip3 show passlib
WARNING: Package(s) not found: passlib
root@instance:/# apt install python3-passlib
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  python3-passlib
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 476 kB of archives.
After this operation, 2091 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble/main amd64 python3-passlib all 1.7.4-4 [476 kB]
Fetched 476 kB in 2s (316 kB/s)
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79, <STDIN> line 1.)
debconf: falling back to frontend: Readline
Selecting previously unselected package python3-passlib.
(Reading database ... 18571 files and directories currently installed.)
Preparing to unpack .../python3-passlib_1.7.4-4_all.deb ...
Unpacking python3-passlib (1.7.4-4) ...
Setting up python3-passlib (1.7.4-4) ...
root@instance:/# pip3 show passlib
Name: passlib
Version: 1.7.4
Summary: comprehensive password hashing framework supporting over 30 schemes
Home-page: https://passlib.readthedocs.io
Author: Eli Collins
Author-email: elic@assurancetechnologies.com
License: BSD
Location: /usr/lib/python3/dist-packages
Requires:
Required-by:
root@instance:/# dpkg -l | grep passlib
ii  python3-passlib               1.7.4-4                           all          comprehensive password hashing framework

Note I did not install passlib using pip, but since apt install passlib in the /usr/lib/python3/dist-packages pip has access to it.

After this I tried again and still I get the same error

TASK [darsh12.bootstrap : Add the user to the system] **************************
fatal: [instance]: FAILED! => {"msg": "Unable to encrypt nor hash, passlib must be installed. No module named 'passlib'. Unable to encrypt nor hash, passlib must be installed. No module named 'passlib'"}

What is the task that you’re executing?

I want to add a user

- name: Add the user to the system
  ansible.builtin.user:
    name: "{{ new_username }}"
    shell: /bin/bash
    groups: sudo
    append: yes
    create_home: yes
    uid: "{{ user_uid | default(omit) }}"
    password: "{{ user_password | password_hash('sha512') }}"
  when: new_username is defined
  become: yes

It is the password: "{{ user_password | password_hash('sha512') }}" task that is requiring the passlib library. Specifically the password_hash expression. If I remove that the task completes successfully

Solution

I have been to find a solution to this. All this time I had been installing passlib in the client machine and also played with custom filter_plugins. I then decided to install passlib on the ansible host/controller machine and what do you know it works.

TLDR; Installing passlib on the ansible host fixed the issue.

The reason why it fixed is the problem is the error was coming from the template

“{{ user_password | password_hash(‘sha512’) }}”

So before the user module ran on the target host it was trying to run password_hash and all filters/plugins that aren’t modules are run in the same python instance as Ansible itself.

Thank you for the explanation, that’s helps

The docs on https://docs.ansible.com/ansible/latest/collections/ansible/builtin/user_module.html#parameter-password do have a link to https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module. But the latter does not make it very explicit that it has to be installed on the management node.
This highlights the importance of showing all the relevant information, in this case the task.