templier-ansible/vars_plugins/pass.py

103 lines
3.8 KiB
Python

#!/usr/bin/env python
from functools import lru_cache
from getpass import getpass
import os
from pathlib import Path
import subprocess
import sys
from ansible.module_utils.six.moves import configparser
from ansible.plugins.vars import BaseVarsPlugin
DOCUMENTATION = """
module: pass
vars: vault
version_added: 2.9
short_description: Load vault passwords from pass
description:
- Works exactly as a vault, loading variables from pass.
- Decrypts the YAML file `ansible_vault` from cranspasswords.
- Loads the secret variables.
- Makes use of data caching in order to avoid calling cranspasswords multiple times.
- Uses the local gpg key from the user running ansible on the Control node.
"""
class VarsModule(BaseVarsPlugin):
@staticmethod
@lru_cache
def decrypt_password(name, crans_submodule=False):
"""
Passwords are decrypted from the local password store, then are cached.
By that way, we don't decrypt these passwords everytime.
"""
# Load config
config = configparser.ConfigParser()
config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pass.ini'))
password_store = Path(config.get('pass', 'password_store_dir',
fallback=os.getenv('PASSWORD_STORE_DIR', Path.home() / '.password-store')))
if crans_submodule:
password_store /= config.get('pass', 'crans_password_store_submodule',
fallback=os.getenv('CRANS_PASSWORD_STORE_SUBMODULE', 'crans'))
full_command = ['gpg', '-d', password_store / f'{name}.gpg']
proc = subprocess.run(full_command, capture_output=True, close_fds=True)
clear_text = proc.stdout.decode('UTF-8')
sys.stderr.write(proc.stderr.decode('UTF-8'))
return clear_text
@staticmethod
@lru_cache
def become_password(entity):
"""
Query the become password that should be used for the given entity.
If entity is the whole group that has no default password,
the become password will be prompted.
The configuration should be given in pass.ini, in the `pass_become`
group. You have only to write `group=pass-filename`.
"""
# Load config
config = configparser.ConfigParser()
config.read(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'pass.ini'))
if config.has_option('pass_become', entity.get_name()):
return VarsModule.decrypt_password(
config.get('pass_become', entity.get_name())).split('\n')[0]
if entity.get_name() == "all":
return getpass("BECOME password: ", stream=None)
return None
def get_vars(self, loader, path, entities):
"""
Get all vars for entities, called by Ansible.
loader: Ansible's DataLoader.
path: Current play's playbook directory.
entities: Host or group names pertinent to the variables needed.
"""
# VarsModule objects are called every time you need host vars, per host,
# and per group the host is part of.
# It is about 6 times per host per task in current state
# of Ansible Crans configuration.
# It is way to much.
# So we cache the data into the DataLoader (see parsing/DataLoader).
passwords = {}
for entity in entities:
# Load vault passwords
if entity.get_name() == 'all':
passwords['vault'] = loader.load(
VarsModule.decrypt_password('ansible_vault', True))
# Load become password
become_password = VarsModule.become_password(entity)
if become_password is not None:
passwords['ansible_become_password'] = become_password
return passwords