Agents get `op` so op:// references resolve at command time and secret values stay out of prompts and transcripts. Installed per 1Password's apt instructions (signed-by keyring, debsig policy + keyring, 1password-cli), with the key fingerprint-checked before it is trusted; EL via their yum repo. multica_op_version pins, "" tracks the repo. Tag: multica_op. Optional, default-off hook writes OP_SERVICE_ACCOUNT_TOKEN from the playbook (ansible-vault) to a 0600 file owned by the daemon user. It is deliberately not wired into the daemon environment: the user unit belongs to the multica CLI, and the delivery design is pending. Rolled out with --tags multica_op to haunted-mansion and spaceship-earth; second run changed=0, op 2.41.1 as multica, debsig verifies the .deb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
188 lines
6.6 KiB
YAML
188 lines
6.6 KiB
YAML
---
|
|
# 1Password CLI from 1Password's own signed package repository, never a
|
|
# curl-piped binary. Mirrors https://developer.1password.com/docs/cli/get-started
|
|
# (Linux, APT / YUM), with the signing key's fingerprint checked before apt or
|
|
# dnf is allowed to trust it.
|
|
|
|
- name: 1Password CLI | Require a supported OS family
|
|
ansible.builtin.assert:
|
|
that:
|
|
- ansible_facts['os_family'] in ['Debian', 'RedHat']
|
|
fail_msg: >-
|
|
The 1Password CLI install supports Debian- and RedHat-family hosts only
|
|
(found {{ ansible_facts['os_family'] }}). Set multica_op_install: false.
|
|
quiet: true
|
|
|
|
# --- Debian / Ubuntu ---------------------------------------------------------
|
|
|
|
- name: 1Password CLI | Install gnupg and debsig-verify
|
|
ansible.builtin.apt:
|
|
name: [gnupg, debsig-verify]
|
|
state: present
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: 1Password CLI | Fetch the signing key
|
|
ansible.builtin.get_url:
|
|
url: "{{ multica_op_key_url }}"
|
|
dest: /usr/share/keyrings/1password-archive-keyring.asc
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
register: _multica_op_key_asc
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: 1Password CLI | Read the signing key's fingerprint
|
|
ansible.builtin.command:
|
|
argv: [gpg, --show-keys, --with-colons, /usr/share/keyrings/1password-archive-keyring.asc]
|
|
register: _multica_op_key_info
|
|
changed_when: false
|
|
check_mode: false
|
|
become: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- not (ansible_check_mode and _multica_op_key_asc is changed)
|
|
|
|
# Under --check on a fresh host the key was only pretend-downloaded, so there is
|
|
# nothing to read; the real run checks it.
|
|
# A key that does not match is never dearmored, so apt never trusts it.
|
|
- name: 1Password CLI | Require the expected signing key
|
|
ansible.builtin.assert:
|
|
that:
|
|
- multica_op_key_fingerprint in (_multica_op_key_info.stdout_lines
|
|
| select('match', '^fpr:') | map('split', ':') | map(attribute=9) | list)
|
|
fail_msg: >-
|
|
{{ multica_op_key_url }} is not the key with fingerprint
|
|
{{ multica_op_key_fingerprint }}. Refusing to trust it.
|
|
quiet: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- _multica_op_key_info is not skipped
|
|
|
|
- name: 1Password CLI | Look for the dearmored keyrings
|
|
ansible.builtin.stat:
|
|
path: "{{ item }}"
|
|
loop:
|
|
- /usr/share/keyrings/1password-archive-keyring.gpg
|
|
- /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg
|
|
register: _multica_op_keyrings
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: 1Password CLI | Create the debsig policy and keyring directories
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
loop:
|
|
- /etc/debsig/policies/AC2D62742012EA22
|
|
- /usr/share/debsig/keyrings/AC2D62742012EA22
|
|
register: _multica_op_debsig_dirs
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
# Same key, two consumers: apt (repository metadata) and debsig-verify (the
|
|
# signature on each .deb).
|
|
- name: 1Password CLI | Dearmor the key for apt and debsig
|
|
ansible.builtin.command:
|
|
argv: [gpg, --batch, --yes, --dearmor, --output, "{{ item.item }}", /usr/share/keyrings/1password-archive-keyring.asc]
|
|
loop: "{{ _multica_op_keyrings.results }}"
|
|
loop_control:
|
|
label: "{{ item.item }}"
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- _multica_op_key_asc is changed or not item.stat.exists
|
|
changed_when: true
|
|
become: true
|
|
|
|
- name: 1Password CLI | Install the debsig policy
|
|
ansible.builtin.get_url:
|
|
url: "{{ multica_op_debsig_policy_url }}"
|
|
dest: /etc/debsig/policies/AC2D62742012EA22/1password.pol
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
become: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- not (ansible_check_mode and _multica_op_debsig_dirs is changed)
|
|
|
|
- name: 1Password CLI | Add the apt repository
|
|
ansible.builtin.apt_repository:
|
|
repo: >-
|
|
deb [arch={{ _multica_arch }} signed-by=/usr/share/keyrings/1password-archive-keyring.gpg]
|
|
{{ multica_op_apt_base_url }}/{{ _multica_arch }} stable main
|
|
filename: 1password
|
|
state: present
|
|
update_cache: true
|
|
register: _multica_op_repo_deb
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'Debian'
|
|
|
|
- name: 1Password CLI | Install the package
|
|
ansible.builtin.apt:
|
|
name: >-
|
|
{{ '1password-cli=' ~ multica_op_version ~ '*'
|
|
if multica_op_version | length > 0 else '1password-cli' }}
|
|
state: "{{ 'present' if multica_op_version | length > 0 else 'latest' }}"
|
|
allow_downgrade: "{{ multica_op_version | length > 0 }}"
|
|
register: _multica_op_pkg_deb
|
|
become: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'Debian'
|
|
- not (ansible_check_mode and _multica_op_repo_deb is changed)
|
|
|
|
# --- EL ----------------------------------------------------------------------
|
|
|
|
- name: 1Password CLI | Add the yum repository
|
|
ansible.builtin.yum_repository:
|
|
name: 1password
|
|
description: 1Password Stable Channel
|
|
baseurl: "{{ multica_op_rpm_base_url }}/$basearch"
|
|
gpgkey: "{{ multica_op_key_url }}"
|
|
gpgcheck: true
|
|
repo_gpgcheck: true
|
|
enabled: true
|
|
register: _multica_op_repo_rpm
|
|
become: true
|
|
when: ansible_facts['os_family'] == 'RedHat'
|
|
|
|
- name: 1Password CLI | Install the package
|
|
ansible.builtin.dnf:
|
|
name: >-
|
|
{{ '1password-cli-' ~ multica_op_version
|
|
if multica_op_version | length > 0 else '1password-cli' }}
|
|
state: "{{ 'present' if multica_op_version | length > 0 else 'latest' }}"
|
|
allow_downgrade: "{{ multica_op_version | length > 0 }}"
|
|
register: _multica_op_pkg_rpm
|
|
become: true
|
|
when:
|
|
- ansible_facts['os_family'] == 'RedHat'
|
|
- not (ansible_check_mode and _multica_op_repo_rpm is changed)
|
|
|
|
# --- Verify ------------------------------------------------------------------
|
|
|
|
# As the daemon user, through its own environment: that is the context agents
|
|
# call `op` from.
|
|
- name: 1Password CLI | Check the installed version as the daemon user
|
|
ansible.builtin.command:
|
|
argv: [op, --version]
|
|
become: true
|
|
become_user: "{{ multica_user }}"
|
|
environment: "{{ _multica_user_env }}"
|
|
register: _multica_op_version_out
|
|
changed_when: false
|
|
check_mode: false
|
|
failed_when: >-
|
|
_multica_op_version_out.rc != 0
|
|
or (multica_op_version | length > 0
|
|
and _multica_op_version_out.stdout | trim != multica_op_version)
|
|
# Under --check, only when the binary is already there to ask.
|
|
when: >-
|
|
not ansible_check_mode
|
|
or (_multica_op_pkg_deb is not skipped and _multica_op_pkg_deb is not changed)
|
|
or (_multica_op_pkg_rpm is not skipped and _multica_op_pkg_rpm is not changed)
|