Skip to content

Commit

Permalink
feat: support podman_credential_files
Browse files Browse the repository at this point in the history
Feature: The parameter podman_credential_files is used to provide containers-auth.json
files which allow authentication to registries.  See README.md for more infomation.

Reason: Users need a way to provide credential files for authenticating to private
registries.  Some operations may need to pull images from registries in an
automated or unattended way, and cannot use `registry_username` and `registry_password`.

Result: Users can provide registry credentials for automated and
unattended operations.

QE: The file tests_auth_and_security.yml has been extended to test this feature.

Signed-off-by: Rich Megginson <[email protected]>
  • Loading branch information
richm committed Apr 22, 2024
1 parent be33b2d commit 9328c0d
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 6 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,62 @@ must also set `podman_registry_username`. You can override this on a per-spec
basis with `registry_password`. The use of `container_image_password` was
unsupported and is deprecated.

### podman_credential_files

This is a `list`. Each element of the list is a `dict` describing a podman
credential file used to authenticate to registries. See `man
containers-auth.json` and `man containers-registries.conf`:`credential-helpers`
for more information about the format of these files, and the default directory
search path.
NOTE: These files contain authentication credentials. Please be careful with
them. You are strongly encouraged to use Ansible Vault to encrypt them.
The keys of each `dict` are as follows:

* `state` - default is `present`. Use `absent` to remove files.
* `file_src` - This is the name of a file on the controller node which will be
copied to `file` on the managed node. Do not specify this if you specify
`file_content` or `template_src`, which will take precedence over `file_src`.
* `template_src` - This is the name of a file on the controller node which will
be templated using the `template` module and copied to `file` on the managed
node. Do not specify this if you specify `file_content` or `file_src`.
* `file_content` - This is a string in `containers-auth.json` format. It will be
used as the contents of `file` on the managed node. Do not specify this if
you specify `file_src` or `template_src`.
* `file` - This is the name of a file on the managed node that will contain the
`auth.json` contents. The default value will be
`$HOME/.config/containers/auth.json`. If you specify a relative path, it will
be relative to `$HOME/.config/containers`. If you specify something other
than the defaults mentioned in `man containers-auth.json`, you will also need
to configure `credential-helpers` in `containers-registries.conf` using
`podman_registries_conf`. Any missing parent directories will be created.
* `run_as_user` - Use this to specify a per-credential file owner. If you do
not specify this, then the global default `podman_run_as_user` value will be
used. Otherwise, `root` will be used. NOTE: The user must already exist - the
role will not create one. The user must be present in `/etc/subuid`. NOTE:
This is used as the user for the `$HOME` directory if `file` is not specified,
and as the owner of the file. If you want the owner of the file to be
different than the user used for `$HOME`, specify `file` as an absolute path.
* `run_as_group` - Use this to specify a per-credential file group. If you do
not specify this, then the global default `podman_run_as_group` value will be
used. Otherwise, `root` will be used. NOTE: The group must already exist -
the role will not create one. The group must be present in `/etc/subgid`.
* `mode` - The mode of the file - default is `"0600"`.

For example, if you have

```yaml
podman_credential_files:
- file_src: auth.json
run_as_user: my_user
```

The local file `auth.json` will be looked up in the usual Ansible `file` search
paths and will be copied to the file
`/home/my_user/.config/containers/auth.json` on the managed node. The file
owner will be `my_user` and the mode will be `"0600"`. The directories
`/home/my_user/.config` and `/home/my_user/.config/containers` will be created
if they do not exist.

## Variables Exported by the Role

### podman_version
Expand Down
7 changes: 7 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,10 @@ podman_registry_username: "{{ container_image_user | d('') }}"
# password to use to authenticate to the registry
# override by specifying registry_password on a per-spec basis
podman_registry_password: "{{ container_image_password | d('') }}"

# List of files containing registry credentials
# If not using the default containers-auth.json, you may need to
# also provide an entry for "credential-helpers" in the registries.conf
# For more information, see "man containers-auth.json" and
# "man containers-registries.conf"
podman_credential_files: []
96 changes: 96 additions & 0 deletions tasks/handle_credential_files.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SPDX-License-Identifier: MIT
---
- name: Set user and group
set_fact:
__podman_credential_user: "{{ __podman_credential_item['run_as_user']
if 'run_as_user' in __podman_credential_item else podman_run_as_user }}"
__podman_credential_group: "{{ __podman_credential_item['run_as_group']
if 'run_as_group' in __podman_credential_item else podman_run_as_group }}"

# NOTE: Sets __podman_group that we use below
- name: Check user and group information
include_tasks: handle_user_group.yml
vars:
__podman_user: "{{ __podman_credential_user }}"
__podman_spec_item: "{{ __podman_credential_item }}"

- name: Set credential variables
set_fact:
__podman_credential_str: "{{ __podman_credential_item['file_content']
if 'file_content' in __podman_credential_item
else lookup('template', __podman_credential_item['template_src'])
if 'template_src' in __podman_credential_item
else none }}"
__podman_credential_file_src: "{{ __podman_credential_item['file_src']
if 'file_src' in __podman_credential_item
else none }}"
__podman_credential_file: "{{ __authdir ~ 'auth.json'
if not 'file' in __podman_credential_item
else __authdir ~ __podman_credential_item['file']
if not __podman_credential_item['file'] is abs
else __podman_credential_item['file'] }}"
__podman_credential_mode: "{{ __podman_credential_item['mode']
if 'mode' in __podman_credential_item else '0600' }}"
__podman_credential_state: "{{ __podman_credential_item['state']
if 'state' in __podman_credential_item else 'present' }}"
vars:
__authdir: "{{
ansible_facts['getent_passwd'][__podman_credential_user][4] ~
'/.config/containers/' }}"
no_log: true

- name: Handle state present
when: __podman_credential_state == "present"
block:
- name: Ensure the credentials directory is present
file:
path: "{{ __podman_credential_file | dirname }}"
state: directory
owner: "{{ __podman_credential_user }}"
group: "{{ __podman_group }}"
mode: "0700"

- name: Ensure credential file is copied
copy:
src: "{{ __podman_credential_file_src }}"
dest: "{{ __podman_credential_file }}"
owner: "{{ __podman_credential_user }}"
group: "{{ __podman_group }}"
mode: "{{ __podman_credential_mode }}"
when: __podman_credential_file_src | length > 0
no_log: true

- name: Ensure credential file content is present
copy:
content: "{{ __podman_credential_str }}"
dest: "{{ __podman_credential_file }}"
owner: "{{ __podman_credential_user }}"
group: "{{ __podman_group }}"
mode: "{{ __podman_credential_mode }}"
when:
- __podman_credential_str | length > 0
- not __podman_credential_file_src
no_log: true

- name: Handle state absent
when: __podman_credential_state == "absent"
block:
- name: Ensure credential file is absent
file:
path: "{{ __podman_credential_file }}"
state: absent
no_log: true

- name: Find files in credentials directory
find:
path: "{{ __podman_credential_file | dirname }}"
file_type: any
hidden: true
register: __credential_dir_files
no_log: true

- name: Ensure the credentials directory is absent if empty
file:
path: "{{ __podman_credential_file | dirname }}"
state: absent
when: __credential_dir_files.matched == 0
5 changes: 3 additions & 2 deletions tasks/handle_user_group.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
- name: Set group for podman user
set_fact:
__podman_group: |-
{%- if "run_as_group" in __podman_spec_item -%}
{{ __podman_spec_item["run_as_group"] }}
{% set item_group = __podman_spec_item.get("run_as_group") %}
{%- if item_group and item_group | length > 0 -%}
{{ item_group }}
{%- elif podman_run_as_group is not none -%}
{{ podman_run_as_group }}
{%- else -%}
Expand Down
7 changes: 7 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@
loop_var: __podman_secret_item
no_log: true

- name: Handle credential files
include_tasks: handle_credential_files.yml
loop: "{{ podman_credential_files }}"
loop_control:
loop_var: __podman_credential_item
no_log: true

- name: Handle Kubernetes specifications
include_tasks: handle_kube_spec.yml
loop: "{{ podman_kube_specs }}"
Expand Down
89 changes: 85 additions & 4 deletions tests/tests_auth_and_security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
ContainerName: auth_test_1_quadlet
podman_kube_spec_base:
state: started
registry_username: "{{ __podman_test_username }}"
registry_password: "{{ __podman_test_password }}"
kube_file_content:
apiVersion: v1
kind: Pod
Expand Down Expand Up @@ -68,11 +66,23 @@
rescue:
- name: Check error
fail:
msg: Role did not fail as expected
when: "'UNREACHABLE' in ansible_failed_result.msg"
msg: >-
Role did not fail with the expected message {{ expected_msg }} but
instead failed with {{ ansible_failed_result | to_nice_json }}
when: not expected_msg in ansible_failed_result.results[0].msg | d("")
vars:
expected_msg: Failed to pull image

- name: Run remaining tasks in block with cleanup
block:
- name: Create a local tmpdir
tempfile:
prefix: lsr_
suffix: _podman
state: directory
register: __local_tmpdir
delegate_to: localhost

- name: Run the role with credentials in spec
include_role:
name: linux-system-roles.podman
Expand All @@ -95,6 +105,77 @@
podman_kube_specs:
- "{{ podman_kube_spec_base }}"

- name: Get authfile locally
fetch:
src: "{{ __podman_test_authfile }}"
dest: "{{ __local_tmpdir.path ~ '/' }}"
flat: true

- name: Provide a credentials file - root
include_role:
name: linux-system-roles.podman
vars:
podman_credential_files:
- file_src: "{{ __local_tmpdir.path ~ '/auth.json' }}"
file: /root/.config/containers/auth.json
podman_quadlet_specs:
- "{{ podman_quadlet_spec_base }}"
podman_kube_specs:
- "{{ podman_kube_spec_base }}"

- name: Create a user for rootless
user:
name: auth_test_user1
uid: 2001

- name: Provide a credentials file - rootless
include_role:
name: linux-system-roles.podman
vars:
podman_credential_files:
- template_src: "{{ __local_tmpdir.path ~ '/auth.json' }}"
run_as_user: auth_test_user1
__run_as_user:
run_as_user: auth_test_user1
podman_quadlet_specs:
- "{{ podman_quadlet_spec_base | combine(__run_as_user) }}"
podman_kube_specs:
- "{{ podman_kube_spec_base | combine(__run_as_user) }}"

always:
- name: Remove all container resources - root
include_role:
name: linux-system-roles.podman
vars:
podman_kube_specs:
- "{{ podman_kube_spec_base | combine({'state': 'absent'}) }}"
podman_quadlet_specs:
- "{{ podman_quadlet_spec_base | combine({'state': 'absent'}) }}"
podman_credential_files:
- state: absent

- name: Remove pods and units - rootless
include_role:
name: linux-system-roles.podman
vars:
podman_run_as_user: auth_test_user1
podman_kube_specs:
- "{{ podman_kube_spec_base | combine({'state': 'absent'}) }}"
podman_quadlet_specs:
- "{{ podman_quadlet_spec_base | combine({'state': 'absent'}) }}"
podman_credential_files:
- state: absent

- name: Remove user
user:
name: auth_test_user1
state: absent

- name: Remove local tmpdir
file:
path: "{{ __local_tmpdir.path }}"
state: absent
delegate_to: localhost

- name: Clean up registry
include_tasks: tasks/cleanup_registry.yml

0 comments on commit 9328c0d

Please sign in to comment.