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.
This commit is contained in:
parent
32b38d4e29
commit
e7eebb6954
3 changed files with 44 additions and 23 deletions
|
@ -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):
|
||||||
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,6 +99,20 @@ class VaultCLI(CLI):
|
||||||
|
|
||||||
self.execute()
|
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):
|
def execute_create(self):
|
||||||
|
|
||||||
if len(self.args) > 1:
|
if len(self.args) > 1:
|
||||||
|
@ -94,13 +120,6 @@ class VaultCLI(CLI):
|
||||||
|
|
||||||
self.editor.create_file(self.args[0])
|
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):
|
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):
|
||||||
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
|
||||||
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:
|
||||||
# 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:
|
||||||
|
|
||||||
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:
|
||||||
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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue