From e7eebb6954bdd6e8d684c8062fe2a6d79c6f213a Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Wed, 26 Aug 2015 22:51:20 +0530 Subject: [PATCH] Implement cat-like filtering behaviour for encrypt/decrypt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the following invocations: # Interactive use, like gpg ansible-vault encrypt --output x # Non-interactive, for scripting echo plaintext|ansible-vault encrypt --output x # Separate input and output files ansible-vault encrypt input.yml --output output.yml # Existing usage (in-place encryption) unchanged ansible-vault encrypt inout.yml …and the analogous cases for ansible-vault decrypt as well. In all cases, the input and output files can be '-' to read from stdin or write to stdout. This permits sensitive data to be encrypted and decrypted without ever hitting disk. --- lib/ansible/cli/__init__.py | 2 ++ lib/ansible/cli/vault.py | 42 +++++++++++++++++---------- lib/ansible/parsing/vault/__init__.py | 23 ++++++++++----- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py index 0106b80cf6..3b9be15760 100644 --- a/lib/ansible/cli/__init__.py +++ b/lib/ansible/cli/__init__.py @@ -262,6 +262,8 @@ class CLI(object): parser.add_option('--new-vault-password-file', dest='new_vault_password_file', help="new vault password file for rekey", action="callback", callback=CLI.expand_tilde, type=str) + parser.add_option('--output', default=None, dest='output_file', + help='output file name for encrypt or decrypt; use - for stdout') if subset_opts: diff --git a/lib/ansible/cli/vault.py b/lib/ansible/cli/vault.py index bae7377750..246a29e1e4 100644 --- a/lib/ansible/cli/vault.py +++ b/lib/ansible/cli/vault.py @@ -63,7 +63,19 @@ class VaultCLI(CLI): self.options, self.args = self.parser.parse_args() self.display.verbosity = self.options.verbosity - if len(self.args) == 0: + if self.options.output_file: + if self.action not in ['encrypt','decrypt']: + raise AnsibleOptionsError("The --output option can be used only with ansible-vault encrypt/decrypt") + + # This restriction should remain in place until it's possible to + # load multiple YAML records from a single file, or it's too easy + # to create an encrypted file that can't be read back in. But in + # the meanwhile, "cat a b c|ansible-vault encrypt --output x" is + # a workaround. + if len(self.args) > 1: + raise AnsibleOptionsError("At most one input file may be used with the --output option") + + elif len(self.args) == 0: raise AnsibleOptionsError("Vault requires at least one filename as a parameter") def run(self): @@ -87,6 +99,20 @@ class VaultCLI(CLI): self.execute() + def execute_encrypt(self): + + for f in self.args or ['-']: + self.editor.encrypt_file(f, output_file=self.options.output_file) + + self.display.display("Encryption successful", stderr=True) + + def execute_decrypt(self): + + for f in self.args or ['-']: + self.editor.decrypt_file(f, output_file=self.options.output_file) + + self.display.display("Decryption successful", stderr=True) + def execute_create(self): if len(self.args) > 1: @@ -94,13 +120,6 @@ class VaultCLI(CLI): self.editor.create_file(self.args[0]) - def execute_decrypt(self): - - for f in self.args: - self.editor.decrypt_file(f) - - self.display.display("Decryption successful", stderr=True) - def execute_edit(self): for f in self.args: self.editor.edit_file(f) @@ -110,13 +129,6 @@ class VaultCLI(CLI): for f in self.args: self.editor.view_file(f) - def execute_encrypt(self): - - for f in self.args: - self.editor.encrypt_file(f) - - self.display.display("Encryption successful", stderr=True) - def execute_rekey(self): for f in self.args: if not (os.path.isfile(f)): diff --git a/lib/ansible/parsing/vault/__init__.py b/lib/ansible/parsing/vault/__init__.py index e8636cad11..e7c60611e8 100644 --- a/lib/ansible/parsing/vault/__init__.py +++ b/lib/ansible/parsing/vault/__init__.py @@ -20,6 +20,7 @@ __metaclass__ = type import os import shlex import shutil +import sys import tempfile from io import BytesIO from subprocess import call @@ -258,21 +259,21 @@ class VaultEditor: # and restore umask os.umask(old_umask) - def encrypt_file(self, filename): + def encrypt_file(self, filename, output_file=None): check_prereqs() plaintext = self.read_data(filename) ciphertext = self.vault.encrypt(plaintext) - self.write_data(ciphertext, filename) + self.write_data(ciphertext, output_file or filename) - def decrypt_file(self, filename): + def decrypt_file(self, filename, output_file=None): check_prereqs() ciphertext = self.read_data(filename) plaintext = self.vault.decrypt(ciphertext) - self.write_data(plaintext, filename) + self.write_data(plaintext, output_file or filename) def create_file(self, filename): """ create a new encrypted file """ @@ -327,7 +328,10 @@ class VaultEditor: def read_data(self, filename): try: - f = open(filename, "rb") + if filename == '-': + f = sys.stdin + else: + f = open(filename, "rb") data = f.read() f.close() except Exception as e: @@ -336,9 +340,12 @@ class VaultEditor: return data def write_data(self, data, filename): - if os.path.isfile(filename): - os.remove(filename) - f = open(filename, "wb") + if filename == '-': + f = sys.stdout + else: + if os.path.isfile(filename): + os.remove(filename) + f = open(filename, "wb") f.write(to_bytes(data, errors='strict')) f.close()