Read become password from pass
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
This commit is contained in:
		@@ -1,13 +1,15 @@
 | 
			
		||||
[defaults]
 | 
			
		||||
# Explicitely redefined some defaults to make play execution work
 | 
			
		||||
roles_path = ./roles
 | 
			
		||||
vars_plugins = ./vars_plugins
 | 
			
		||||
 | 
			
		||||
inventory = ./hosts
 | 
			
		||||
timeout = 60
 | 
			
		||||
 | 
			
		||||
[privilege_escalation]
 | 
			
		||||
become = True
 | 
			
		||||
become_ask_pass = True
 | 
			
		||||
# Use a separate module to read passwords from pass
 | 
			
		||||
become_ask_pass = False
 | 
			
		||||
 | 
			
		||||
[ssh_connection]
 | 
			
		||||
pipelining = True
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								vars_plugins/__pycache__/pass.cpython-39.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vars_plugins/__pycache__/pass.cpython-39.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vars_plugins/__pycache__/vault_cranspasswords.cpython-39.pyc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vars_plugins/__pycache__/vault_cranspasswords.cpython-39.pyc
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										6
									
								
								vars_plugins/pass.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vars_plugins/pass.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
[pass]
 | 
			
		||||
# password_store_dir=/home/ynerant/.password-store
 | 
			
		||||
# crans_password_store_submodule=crans
 | 
			
		||||
 | 
			
		||||
[pass_become]
 | 
			
		||||
all=templier
 | 
			
		||||
							
								
								
									
										6
									
								
								vars_plugins/pass.ini.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vars_plugins/pass.ini.example
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										102
									
								
								vars_plugins/pass.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
		Reference in New Issue
	
	Block a user