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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
# Copyright (c) 2022 Hai Cao, <t-haicao@microsoft.com>, Marcin Slowikowski (@msl0)
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
---
name: azure_keyvault_secret
author:
- Hai Cao (@tk5eq) <t-haicao@microsoft.com>
- Marcin Slowikowski (@msl0)
version_added: '1.12.0'
requirements:
- requests
- azure
short_description: Read secret from Azure Key Vault.
description:
- This lookup returns the content of secret saved in Azure Key Vault.
- When ansible host is MSI enabled Azure VM, user don't need provide any credential to access to Azure Key Vault.
options:
_terms:
description: Secret name, version can be included like secret_name/secret_version.
required: True
vault_url:
description: Url of Azure Key Vault.
required: True
client_id:
description: Client id of service principal that has access to the Azure Key Vault
secret:
description: Secret of the service principal.
tenant_id:
description: Tenant id of service principal.
notes:
- If version is not provided, this plugin will return the latest version of the secret.
- If ansible is running on Azure Virtual Machine with MSI enabled, client_id, secret and tenant isn't required.
- For enabling MSI on Azure VM, please refer to this doc https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/
- After enabling MSI on Azure VM, remember to grant access of the Key Vault to the VM by adding a new Acess Policy in Azure Portal.
- If MSI is not enabled on ansible host, it's required to provide a valid service principal which has access to the key vault.
- To authenticate via service principal, pass client_id, secret and tenant_id or set environment variables
AZURE_CLIENT_ID, AZURE_CLIENT_SECRET and AZURE_TENANT_ID.
- Authentication via C(az login) is also supported.
- To use a plugin from a collection, please reference the full namespace, collection name, and lookup plugin name that you want to use.
"""
EXAMPLE = """
- name: Look up secret when azure cli login
debug:
msg: msg: "{{ lookup('azure.azcollection.azure_keyvault_secret', 'testsecret', vault_url=key_vault_uri)}}"
- name: Look up secret when ansible host is MSI enabled Azure VM
debug:
msg: "the value of this secret is {{
lookup(
'azure.azcollection.azure_keyvault_secret',
'testSecret/version',
vault_url='https://yourvault.vault.azure.net'
)
}}"
- name: Look up secret when ansible host is general VM
vars:
url: 'https://yourvault.vault.azure.net'
secretname: 'testSecret/version'
client_id: '123456789'
secret: 'abcdefg'
tenant: 'uvwxyz'
debug:
msg: "the value of this secret is {{
lookup(
'azure.azcollection.azure_keyvault_secret',
secretname,
vault_url=url,
client_id=client_id,
secret=secret,
tenant_id=tenant
)
}}"
# Example below creates an Azure Virtual Machine with SSH public key from key vault using 'azure_keyvault_secret' lookup plugin.
- name: Create Azure VM
hosts: localhost
connection: local
no_log: True
vars:
resource_group: myResourceGroup
vm_name: testvm
location: eastus
ssh_key: "{{ lookup('azure.azcollection.azure_keyvault_secret','myssh_key') }}"
- name: Create VM
azure_rm_virtualmachine:
resource_group: "{{ resource_group }}"
name: "{{ vm_name }}"
vm_size: Standard_DS1_v2
admin_username: azureuser
ssh_password_enabled: false
ssh_public_keys:
- path: /home/azureuser/.ssh/authorized_keys
key_data: "{{ ssh_key }}"
network_interfaces: "{{ vm_name }}"
image:
offer: 0001-com-ubuntu-server-focal
publisher: Canonical
sku: 20_04-lts
version: latest
"""
RETURN = """
_raw:
description: secret content string
"""
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible.utils.display import Display
try:
import logging
import requests
from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential, ClientSecretCredential
from azure.keyvault.secrets import SecretClient
except ImportError:
pass
display = Display()
TOKEN_ACQUIRED = False
logger = logging.getLogger("azure.identity").setLevel(logging.ERROR)
token_params = {
'api-version': '2018-02-01',
'resource': 'https://vault.azure.net'
}
token_headers = {
'Metadata': 'true'
}
token = None
try:
token_res = requests.get('http://169.254.169.254/metadata/identity/oauth2/token', params=token_params, headers=token_headers, timeout=(3.05, 27))
if token_res.ok:
token = token_res.json().get("access_token")
if token is not None:
TOKEN_ACQUIRED = True
else:
display.v('Successfully called MSI endpoint, but no token was available. Will use service principal if provided.')
else:
display.v("Unable to query MSI endpoint, Error Code %s. Will use service principal if provided" % token_res.status_code)
except Exception:
display.v('Unable to fetch MSI token. Will use service principal if provided.')
TOKEN_ACQUIRED = False
def lookup_secret_non_msi(terms, vault_url, kwargs):
client_id = kwargs['client_id'] if kwargs.get('client_id') else None
secret = kwargs['secret'] if kwargs.get('secret') else None
tenant_id = kwargs['tenant_id'] if kwargs.get('tenant_id') else None
if all(v is not None for v in [client_id, secret, tenant_id]):
credential = ClientSecretCredential(
tenant_id=tenant_id,
client_id=client_id,
client_secret=secret,
)
else:
credential = DefaultAzureCredential()
client = SecretClient(vault_url, credential)
ret = []
for term in terms:
try:
secret_val = client.get_secret(term).value
ret.append(secret_val)
except Exception:
raise AnsibleError('Failed to fetch secret ' + term + '.')
return ret
class LookupModule(LookupBase):
def run(self, terms, variables, **kwargs):
ret = []
vault_url = kwargs.pop('vault_url', None)
if vault_url is None:
raise AnsibleError('Failed to get valid vault url.')
if TOKEN_ACQUIRED:
secret_params = {'api-version': '2016-10-01'}
secret_headers = {'Authorization': 'Bearer ' + token}
for term in terms:
try:
secret_res = requests.get(vault_url + '/secrets/' + term, params=secret_params, headers=secret_headers)
ret.append(secret_res.json()["value"])
except KeyError:
raise AnsibleError('Failed to fetch secret ' + term + '.')
except Exception:
raise AnsibleError('Failed to fetch secret: ' + term + ' via MSI endpoint.')
return ret
else:
return lookup_secret_non_msi(terms, vault_url, kwargs)
|