summaryrefslogtreecommitdiff
path: root/ansible/scripts/vault-keyring-client.py
blob: a0e830fabafddab9f1199f81058a4b7617c14bf4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
55
56
57
58
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python
# (c) 2014, Matt Martz <matt@sivel.net>
# (c) 2016, Justin Mayer <https://justinmayer.com/>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#
# =============================================================================
#
# This script is to be used with ansible-vault's --vault-id arg
# to retrieve the vault password via your OS's native keyring application.
#
# This file *MUST* be saved with executable permissions. Otherwise, Ansible
# will try to parse as a password file and display: "ERROR! Decryption failed"
#
# The `keyring` Python module is required: https://pypi.org/project/keyring/
#
# By default, this script will store the specified password in the keyring of
# the user that invokes the script. To specify a user keyring, add a [vault]
# section to your ansible.cfg file with a 'username' option. Example:
#
# [vault]
# username = 'ansible-vault'
#
# In useage like:
#
#    ansible-vault --vault-id keyring_id@contrib/vault/vault-keyring-client.py view some_encrypted_file
#
#  --vault-id will call this script like:
#
#     contrib/vault/vault-keyring-client.py --vault-id keyring_id
#
# That will retrieve the password from users keyring for the
# keyring service 'keyring_id'. The equivalent of:
#
#      keyring get keyring_id $USER
#
# If no vault-id name is specified to ansible command line, the vault-keyring-client.py
# script will be called without a '--vault-id' and will default to the keyring service 'ansible'
# This is equivalent to:
#
#    keyring get ansible $USER
#
# You can configure the `vault_password_file` option in ansible.cfg:
#
# [defaults]
# ...
# vault_password_file = /path/to/vault-keyring-client.py
# ...
#
# To set your password, `cd` to your project directory and run:
#
#   # will use default keyring service / vault-id of 'ansible'
#   /path/to/vault-keyring-client.py --set
#
# or to specify the keyring service / vault-id of 'my_ansible_secret':
#
#  /path/to/vault-keyring-client.py --vault-id my_ansible_secret --set
#
# If you choose not to configure the path to `vault_password_file` in
# ansible.cfg, your `ansible-playbook` command might look like:
#
# ansible-playbook --vault-id=keyring_id@/path/to/vault-keyring-client.py site.yml

from __future__ import absolute_import, division, print_function

__metaclass__ = type

import argparse
import getpass
import sys

import keyring
from ansible.config.manager import ConfigManager, get_ini_config_value

KEYNAME_UNKNOWN_RC = 2


def build_arg_parser():
    parser = argparse.ArgumentParser(
        description="Get a vault password from user keyring"
    )

    parser.add_argument(
        "--vault-id",
        action="store",
        default=None,
        dest="vault_id",
        help="name of the vault secret to get from keyring",
    )
    parser.add_argument(
        "--username",
        action="store",
        default=None,
        help="the username whose keyring is queried",
    )
    parser.add_argument(
        "--set",
        action="store_true",
        default=False,
        dest="set_password",
        help="set the password instead of getting it",
    )
    return parser


def main():
    # Set default values
    username = getpass.getuser()
    keyname = "ansible"

    # Try to load values from config if one exists
    config = ConfigManager()
    if config._config_file:
        username = (
            get_ini_config_value(
                config._parsers[config._config_file],
                dict(section="vault", key="username"),
            )
            or username
        )

        keyname = (
            get_ini_config_value(
                config._parsers[config._config_file],
                dict(section="vault", key="keyname"),
            )
            or keyname
        )

    # Read values from command line (which override the previous if given)
    arg_parser = build_arg_parser()
    args = arg_parser.parse_args()

    username = args.username or username
    keyname = args.vault_id or keyname

    if args.set_password:
        intro = 'Storing password in "{}" user keyring using key name: {}\n'
        sys.stdout.write(intro.format(username, keyname))
        password = getpass.getpass()
        confirm = getpass.getpass("Confirm password: ")
        if password == confirm:
            keyring.set_password(keyname, username, password)
        else:
            sys.stderr.write("Passwords do not match\n")
            sys.exit(1)
    else:
        secret = keyring.get_password(keyname, username)
        if secret is None:
            sys.stderr.write(
                'vault-keyring-client could not find key="%s" for user="%s" via backend="%s"\n'
                % (keyname, username, keyring.get_keyring().name)
            )
            sys.exit(KEYNAME_UNKNOWN_RC)

        # print('secret: %s' % secret)
        sys.stdout.write("%s\n" % secret)

    sys.exit(0)


if __name__ == "__main__":
    main()