Read become password from pass

Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
This commit is contained in:
Yohann D'ANELLO 2021-04-08 10:48:05 +02:00
parent 475fe06fb7
commit 7f39ea724a
Signed by: ynerant
GPG Key ID: 3A75C55819C8CF85
6 changed files with 117 additions and 1 deletions

View File

@ -1,13 +1,15 @@
[defaults] [defaults]
# Explicitely redefined some defaults to make play execution work # Explicitely redefined some defaults to make play execution work
roles_path = ./roles roles_path = ./roles
vars_plugins = ./vars_plugins
inventory = ./hosts inventory = ./hosts
timeout = 60 timeout = 60
[privilege_escalation] [privilege_escalation]
become = True become = True
become_ask_pass = True # Use a separate module to read passwords from pass
become_ask_pass = False
[ssh_connection] [ssh_connection]
pipelining = True pipelining = True

Binary file not shown.

6
vars_plugins/pass.ini Normal file
View File

@ -0,0 +1,6 @@
[pass]
# password_store_dir=/home/ynerant/.password-store
# crans_password_store_submodule=crans
[pass_become]
all=templier

View File

@ -0,0 +1,6 @@
[pass]
# password_store_dir=/home/me/.password-store
# crans_password_store_submodule=crans
[pass_become]
# all=mdp-root

102
vars_plugins/pass.py Normal file
View File

@ -0,0 +1,102 @@
#!/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