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
|
# (c) 2015, Jonathan Davila <jdavila(at)ansible.com>
#
# This file is part of Ansible
#
# 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/>.
#
# USAGE: {{ lookup('hashi_vault', 'secret=secret/hello:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200')}}
#
# To authenticate with a username/password against the LDAP auth backend in Vault:
#
# USAGE: {{ lookup('hashi_vault', 'secret=secret/hello:value auth_method=ldap mount_point=ldap username=myuser password=mypassword url=http://myvault:8200')}}
#
# The mount_point param defaults to ldap, so is only required if you have a custom mount point.
#
# You can skip setting the url if you set the VAULT_ADDR environment variable
# or if you want it to default to localhost:8200
#
# NOTE: Due to a current limitation in the HVAC library there won't
# necessarily be an error if a bad endpoint is specified.
#
# Requires hvac library. Install with pip.
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
ANSIBLE_HASHI_VAULT_ADDR = 'http://127.0.0.1:8200'
if os.getenv('VAULT_ADDR') is not None:
ANSIBLE_HASHI_VAULT_ADDR = os.environ['VAULT_ADDR']
class HashiVault:
def __init__(self, **kwargs):
try:
import hvac
except ImportError:
raise AnsibleError("Please pip install hvac to use this module")
self.url = kwargs.get('url', ANSIBLE_HASHI_VAULT_ADDR)
# split secret arg, which has format 'secret/hello:value' into secret='secret/hello' and secret_field='value'
s = kwargs.get('secret')
if s is None:
raise AnsibleError("No secret specified")
s_f = s.split(':')
self.secret = s_f[0]
if len(s_f)>=2:
self.secret_field = s_f[1]
else:
self.secret_field = 'value'
# if a particular backend is asked for (and its method exists) we call it, otherwise drop through to using
# token auth. this means if a particular auth backend is requested and a token is also given, then we
# ignore the token and attempt authentication against the specified backend.
#
# to enable a new auth backend, simply add a new 'def auth_<type>' method below.
#
self.auth_method = kwargs.get('auth_method')
if self.auth_method:
try:
self.client = hvac.Client(url=self.url)
# prefixing with auth_ to limit which methods can be accessed
getattr(self, 'auth_' + self.auth_method)(**kwargs)
except AttributeError:
raise AnsibleError("Authentication method '%s' not supported" % self.auth_method)
else:
self.token = kwargs.get('token', os.environ.get('VAULT_TOKEN', None))
if self.token is None and os.environ.get('HOME'):
token_filename = os.path.join(
os.environ.get('HOME'),
'.vault-token'
)
if os.path.exists(token_filename):
with open(token_filename) as token_file:
self.token = token_file.read().strip()
if self.token is None:
raise AnsibleError("No Vault Token specified")
self.client = hvac.Client(url=self.url, token=self.token)
if self.client.is_authenticated():
pass
else:
raise AnsibleError("Invalid authentication credentials specified")
def get(self):
data = self.client.read(self.secret)
if data is None:
raise AnsibleError("The secret %s doesn't seem to exist" % self.secret)
if self.secret_field=='': # secret was specified with trailing ':'
return data['data']
if self.secret_field not in data['data']:
raise AnsibleError("The secret %s does not contain the field '%s'. " % (self.secret, self.secret_field))
return data['data'][self.secret_field]
def auth_ldap(self, **kwargs):
username = kwargs.get('username')
if username is None:
raise AnsibleError("Authentication method ldap requires a username")
password = kwargs.get('password')
if password is None:
raise AnsibleError("Authentication method ldap requires a password")
mount_point = kwargs.get('mount_point')
if mount_point is None:
mount_point = 'ldap'
self.client.auth_ldap(username, password, mount_point)
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
vault_args = terms[0].split(' ')
vault_dict = {}
ret = []
for param in vault_args:
try:
key, value = param.split('=')
except ValueError as e:
raise AnsibleError("hashi_vault plugin needs key=value pairs, but received %s" % terms)
vault_dict[key] = value
vault_conn = HashiVault(**vault_dict)
for term in terms:
key = term.split()[0]
value = vault_conn.get()
ret.append(value)
return ret
|