Commit e7eebb69 by Abhijit Menon-Sen

Implement cat-like filtering behaviour for encrypt/decrypt

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.
parent 32b38d4e
...@@ -262,6 +262,8 @@ class CLI(object): ...@@ -262,6 +262,8 @@ class CLI(object):
parser.add_option('--new-vault-password-file', parser.add_option('--new-vault-password-file',
dest='new_vault_password_file', help="new vault password file for rekey", action="callback", dest='new_vault_password_file', help="new vault password file for rekey", action="callback",
callback=CLI.expand_tilde, type=str) 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: if subset_opts:
......
...@@ -63,7 +63,19 @@ class VaultCLI(CLI): ...@@ -63,7 +63,19 @@ class VaultCLI(CLI):
self.options, self.args = self.parser.parse_args() self.options, self.args = self.parser.parse_args()
self.display.verbosity = self.options.verbosity 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") raise AnsibleOptionsError("Vault requires at least one filename as a parameter")
def run(self): def run(self):
...@@ -87,20 +99,27 @@ class VaultCLI(CLI): ...@@ -87,20 +99,27 @@ class VaultCLI(CLI):
self.execute() self.execute()
def execute_create(self): def execute_encrypt(self):
if len(self.args) > 1: for f in self.args or ['-']:
raise AnsibleOptionsError("ansible-vault create can take only one filename argument") self.editor.encrypt_file(f, output_file=self.options.output_file)
self.editor.create_file(self.args[0]) self.display.display("Encryption successful", stderr=True)
def execute_decrypt(self): def execute_decrypt(self):
for f in self.args: for f in self.args or ['-']:
self.editor.decrypt_file(f) self.editor.decrypt_file(f, output_file=self.options.output_file)
self.display.display("Decryption successful", stderr=True) self.display.display("Decryption successful", stderr=True)
def execute_create(self):
if len(self.args) > 1:
raise AnsibleOptionsError("ansible-vault create can take only one filename argument")
self.editor.create_file(self.args[0])
def execute_edit(self): def execute_edit(self):
for f in self.args: for f in self.args:
self.editor.edit_file(f) self.editor.edit_file(f)
...@@ -110,13 +129,6 @@ class VaultCLI(CLI): ...@@ -110,13 +129,6 @@ class VaultCLI(CLI):
for f in self.args: for f in self.args:
self.editor.view_file(f) 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): def execute_rekey(self):
for f in self.args: for f in self.args:
if not (os.path.isfile(f)): if not (os.path.isfile(f)):
......
...@@ -20,6 +20,7 @@ __metaclass__ = type ...@@ -20,6 +20,7 @@ __metaclass__ = type
import os import os
import shlex import shlex
import shutil import shutil
import sys
import tempfile import tempfile
from io import BytesIO from io import BytesIO
from subprocess import call from subprocess import call
...@@ -258,21 +259,21 @@ class VaultEditor: ...@@ -258,21 +259,21 @@ class VaultEditor:
# and restore umask # and restore umask
os.umask(old_umask) os.umask(old_umask)
def encrypt_file(self, filename): def encrypt_file(self, filename, output_file=None):
check_prereqs() check_prereqs()
plaintext = self.read_data(filename) plaintext = self.read_data(filename)
ciphertext = self.vault.encrypt(plaintext) 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() check_prereqs()
ciphertext = self.read_data(filename) ciphertext = self.read_data(filename)
plaintext = self.vault.decrypt(ciphertext) plaintext = self.vault.decrypt(ciphertext)
self.write_data(plaintext, filename) self.write_data(plaintext, output_file or filename)
def create_file(self, filename): def create_file(self, filename):
""" create a new encrypted file """ """ create a new encrypted file """
...@@ -327,7 +328,10 @@ class VaultEditor: ...@@ -327,7 +328,10 @@ class VaultEditor:
def read_data(self, filename): def read_data(self, filename):
try: try:
f = open(filename, "rb") if filename == '-':
f = sys.stdin
else:
f = open(filename, "rb")
data = f.read() data = f.read()
f.close() f.close()
except Exception as e: except Exception as e:
...@@ -336,9 +340,12 @@ class VaultEditor: ...@@ -336,9 +340,12 @@ class VaultEditor:
return data return data
def write_data(self, data, filename): def write_data(self, data, filename):
if os.path.isfile(filename): if filename == '-':
os.remove(filename) f = sys.stdout
f = open(filename, "wb") else:
if os.path.isfile(filename):
os.remove(filename)
f = open(filename, "wb")
f.write(to_bytes(data, errors='strict')) f.write(to_bytes(data, errors='strict'))
f.close() f.close()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment