ansible-vault 6.79 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
#!/usr/bin/env python

# (c) 2014, James Tanner <tanner.jc@gmail.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#
# ansible-pull is a script that runs ansible in local mode
# after checking out a playbooks directory from source repo.  There is an
# example playbook to bootstrap this script in the examples/ dir which
# installs ansible and sets it up to run on cron.

James Tanner committed
23
import os
24 25 26 27 28
import sys
import traceback

from ansible import utils
from ansible import errors
James Tanner committed
29
from ansible.utils.vault import VaultEditor
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

from optparse import OptionParser

#-------------------------------------------------------------------------------------
# Utility functions for parsing actions/options
#-------------------------------------------------------------------------------------

VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey")

def build_option_parser(action):
    """
    Builds an option parser object based on the action
    the user wants to execute.
    """

    usage = "usage: %%prog [%s] [--help] [options] file_name" % "|".join(VALID_ACTIONS)
    epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
    OptionParser.format_epilog = lambda self, formatter: self.epilog
    parser = OptionParser(usage=usage, epilog=epilog)

    if not action:
        parser.print_help()
        sys.exit()

    # options for all actions
55
    #parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
56 57 58
    parser.add_option('--debug', dest='debug', action="store_true", help="debug")
    parser.add_option('--vault-password-file', dest='password_file',
                    help="vault password file")
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

    # options specific to actions
    if action == "create":
        parser.set_usage("usage: %prog create [options] file_name")
    elif action == "decrypt":
        parser.set_usage("usage: %prog decrypt [options] file_name")
    elif action == "edit":
        parser.set_usage("usage: %prog edit [options] file_name")
    elif action == "encrypt":
        parser.set_usage("usage: %prog encrypt [options] file_name")
    elif action == "rekey":
        parser.set_usage("usage: %prog rekey [options] file_name")
    
    # done, return the parser
    return parser

def get_action(args):
    """
    Get the action the user wants to execute from the 
    sys argv list.
    """
    for i in range(0,len(args)):
        arg = args[i]
        if arg in VALID_ACTIONS:
            del args[i]
            return arg
    return None

def get_opt(options, k, defval=""):
    """
    Returns an option from an Optparse values instance.
    """
    try:
        data = getattr(options, k)
    except:
        return defval
    if k == "roles_path":
        if os.pathsep in data:
            data = data.split(os.pathsep)[0]
    return data

#-------------------------------------------------------------------------------------
# Command functions
#-------------------------------------------------------------------------------------

104 105 106 107
def _read_password(filename):
    f = open(filename, "rb")
    data = f.read()
    f.close
108
    data = data.strip()
109 110
    return data

111 112 113
def execute_create(args, options, parser):

    if len(args) > 1:
James Tanner committed
114
        raise errors.AnsibleError("'create' does not accept more than one filename")        
115 116 117 118 119

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
    else:
        password = _read_password(options.password_file)
120

121
    cipher = 'AES256'
James Tanner committed
122 123 124 125 126
    if hasattr(options, 'cipher'):
        cipher = options.cipher

    this_editor = VaultEditor(cipher, password, args[0])
    this_editor.create_file()
127 128 129

def execute_decrypt(args, options, parser):

130 131 132 133
    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)
134

135
    cipher = 'AES256'
James Tanner committed
136 137 138
    if hasattr(options, 'cipher'):
        cipher = options.cipher

139
    for f in args:
James Tanner committed
140 141
        this_editor = VaultEditor(cipher, password, f)
        this_editor.decrypt_file()
142 143 144 145 146 147 148 149

    print "Decryption successful"

def execute_edit(args, options, parser):

    if len(args) > 1:
        raise errors.AnsibleError("create does not accept more than one filename")        

150 151 152 153
    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)
154

James Tanner committed
155 156
    cipher = None

157
    for f in args:
James Tanner committed
158 159
        this_editor = VaultEditor(cipher, password, f)
        this_editor.edit_file()
160 161 162

def execute_encrypt(args, options, parser):

James Tanner committed
163 164
    if len(args) > 1:
        raise errors.AnsibleError("'create' does not accept more than one filename")        
165 166 167 168 169

    if not options.password_file:        
        password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
    else:
        password = _read_password(options.password_file)
170

171
    cipher = 'AES256'
James Tanner committed
172 173 174
    if hasattr(options, 'cipher'):
        cipher = options.cipher

175
    for f in args:
James Tanner committed
176 177
        this_editor = VaultEditor(cipher, password, f)
        this_editor.encrypt_file()
178 179 180 181 182

    print "Encryption successful"

def execute_rekey(args, options, parser):

183 184 185 186 187 188 189
    if not options.password_file:        
        password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
    else:
        password = _read_password(options.password_file)

    __, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)

James Tanner committed
190
    cipher = None
191
    for f in args:
James Tanner committed
192 193
        this_editor = VaultEditor(cipher, password, f)
        this_editor.rekey_file(new_password)
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

    print "Rekey successful"

#-------------------------------------------------------------------------------------
# MAIN
#-------------------------------------------------------------------------------------

def main():

    action = get_action(sys.argv)
    parser = build_option_parser(action)
    (options, args) = parser.parse_args()

    # execute the desired action
    try:
        fn = globals()["execute_%s" % action]
        fn(args, options, parser)
    except Exception, err:
        if options.debug:
            print traceback.format_exc()
        print "ERROR:",err
        sys.exit(1)

if __name__ == "__main__":
    main()