Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a09cb00

Browse files
authored
Add zabbix_mfa module (ansible-collections#1353)
1 parent 1978473 commit a09cb00

File tree

4 files changed

+432
-0
lines changed

4 files changed

+432
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
minor_changes:
3+
- zabbix_mfa module added

plugins/modules/zabbix_mfa.py

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright: (c) 2024, ONODERA Masaru <[email protected]>
5+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6+
7+
8+
from __future__ import absolute_import, division, print_function
9+
10+
__metaclass__ = type
11+
12+
13+
DOCUMENTATION = """
14+
---
15+
module: zabbix_mfa
16+
17+
short_description: Create/update/delete Zabbix MFA method
18+
19+
20+
description:
21+
- This module allows you to create, update and delete Zabbix MFA method.
22+
23+
author:
24+
- ONODERA Masaru(@masa-orca)
25+
26+
requirements:
27+
- "python >= 3.11"
28+
29+
version_added: 3.1.0
30+
31+
options:
32+
name:
33+
description:
34+
- Name of this MFA method
35+
type: str
36+
required: true
37+
method_type:
38+
description:
39+
- A type of this MFA method
40+
type: str
41+
choices:
42+
- "totp"
43+
- "duo_universal_prompt"
44+
hash_function:
45+
description:
46+
- Type of the hash function for generating TOTP codes.
47+
- Required when C(method_type=totp).
48+
type: str
49+
choices:
50+
- "sha-1"
51+
- "sha-256"
52+
- "sha-512"
53+
code_length:
54+
description:
55+
- Verification code length.
56+
- Required when C(method_type=totp).
57+
type: int
58+
choices:
59+
- 6
60+
- 8
61+
api_hostname:
62+
description:
63+
- API hostname provided by the Duo authentication service.
64+
- Required when C(method_type=duo_universal_prompt).
65+
type: str
66+
clientid:
67+
description:
68+
- Client ID provided by the Duo authentication service.
69+
- Required when C(method_type=duo_universal_prompt).
70+
type: str
71+
client_secret:
72+
description:
73+
- Client secret provided by the Duo authentication service.
74+
- Required when C(method_type=duo_universal_prompt).
75+
type: str
76+
state:
77+
description:
78+
- State of this MFA.
79+
type: str
80+
choices: ['present', 'absent']
81+
default: 'present'
82+
83+
84+
notes:
85+
- Only Zabbix >= 7.0 is supported.
86+
- This module returns changed=true when I(method_type) is C(duo_universal_prompt) as Zabbix API
87+
will not return any sensitive information back for module to compare.
88+
89+
extends_documentation_fragment:
90+
- community.zabbix.zabbix
91+
92+
"""
93+
94+
EXAMPLES = """
95+
# If you want to use Username and Password to be authenticated by Zabbix Server
96+
- name: Set credentials to access Zabbix Server API
97+
ansible.builtin.set_fact:
98+
ansible_user: Admin
99+
ansible_httpapi_pass: zabbix
100+
101+
# If you want to use API token to be authenticated by Zabbix Server
102+
# https://www.zabbix.com/documentation/current/en/manual/web_interface/frontend_sections/administration/general#api-tokens
103+
- name: Set API token
104+
ansible.builtin.set_fact:
105+
ansible_zabbix_auth_key: 8ec0d52432c15c91fcafe9888500cf9a607f44091ab554dbee860f6b44fac895
106+
107+
- name: Create a 'Zabbix TOTP' MFA method
108+
# set task level variables as we change ansible_connection plugin here
109+
vars:
110+
ansible_network_os: community.zabbix.zabbix
111+
ansible_connection: httpapi
112+
ansible_httpapi_port: 443
113+
ansible_httpapi_use_ssl: true
114+
ansible_httpapi_validate_certs: false
115+
ansible_zabbix_url_path: 'zabbixeu' # If Zabbix WebUI runs on non-default (zabbix) path ,e.g. http://<FQDN>/zabbixeu
116+
ansible_host: zabbix-example-fqdn.org
117+
community.zabbix.zabbix_mfa:
118+
name: Zabbix TOTP
119+
method_type: totp
120+
hash_function: sha-1
121+
code_length: 6
122+
"""
123+
124+
RETURN = """
125+
msg:
126+
description: The result of the creating operation
127+
returned: success
128+
type: str
129+
sample: 'Successfully created MFA method'
130+
"""
131+
132+
133+
from ansible.module_utils.basic import AnsibleModule
134+
135+
from ansible_collections.community.zabbix.plugins.module_utils.base import ZabbixBase
136+
from ansible.module_utils.compat.version import LooseVersion
137+
138+
import ansible_collections.community.zabbix.plugins.module_utils.helpers as zabbix_utils
139+
140+
141+
class MFA(ZabbixBase):
142+
def __init__(self, module, zbx=None, zapi_wrapper=None):
143+
super(MFA, self).__init__(module, zbx, zapi_wrapper)
144+
if LooseVersion(self._zbx_api_version) < LooseVersion("7.0"):
145+
module.fail_json(
146+
msg="This module doesn't support Zabbix versions lower than 7.0"
147+
)
148+
149+
def get_mfa(self, mfa_name):
150+
try:
151+
mfas = self._zapi.mfa.get(
152+
{
153+
"output": "extend",
154+
"search": {"name": mfa_name},
155+
}
156+
)
157+
mfa = None
158+
for _mfa in mfas:
159+
if (_mfa["name"] == mfa_name):
160+
mfa = _mfa
161+
return mfa
162+
except Exception as e:
163+
self._module.fail_json(
164+
msg="Failed to get MFA method: %s" % e
165+
)
166+
167+
def delete_mfa(self, mfa):
168+
try:
169+
parameter = [mfa["mfaid"]]
170+
if self._module.check_mode:
171+
self._module.exit_json(changed=True)
172+
self._zapi.mfa.delete(parameter)
173+
self._module.exit_json(
174+
changed=True, msg="Successfully deleted MFA method."
175+
)
176+
except Exception as e:
177+
self._module.fail_json(
178+
msg="Failed to delete MFA method: %s" % e
179+
)
180+
181+
def _convert_to_parameter(self, name, method_type, hash_function, code_length, api_hostname, clientid, client_secret):
182+
parameter = {}
183+
parameter['name'] = name
184+
parameter['type'] = str(zabbix_utils.helper_to_numeric_value(
185+
[
186+
None,
187+
"totp",
188+
"duo_universal_prompt"
189+
],
190+
method_type
191+
))
192+
if (method_type == 'totp'):
193+
parameter['hash_function'] = str(zabbix_utils.helper_to_numeric_value(
194+
[
195+
None,
196+
"sha-1",
197+
"sha-256",
198+
"sha-512"
199+
],
200+
hash_function
201+
))
202+
parameter['code_length'] = str(code_length)
203+
else:
204+
parameter['api_hostname'] = str(api_hostname)
205+
parameter['clientid'] = str(clientid)
206+
parameter['client_secret'] = str(client_secret)
207+
return parameter
208+
209+
def create_mfa(self, name, method_type, hash_function, code_length, api_hostname, clientid, client_secret):
210+
parameter = self._convert_to_parameter(name, method_type, hash_function, code_length, api_hostname, clientid, client_secret)
211+
try:
212+
if self._module.check_mode:
213+
self._module.exit_json(changed=True)
214+
self._zapi.mfa.create(parameter)
215+
self._module.exit_json(
216+
changed=True, msg="Successfully created MFA method."
217+
)
218+
except Exception as e:
219+
self._module.fail_json(
220+
msg="Failed to create MFA method: %s" % e
221+
)
222+
223+
def update_mfa(self, current_mfa, name, method_type, hash_function, code_length, api_hostname, clientid, client_secret):
224+
try:
225+
parameter = self._convert_to_parameter(name, method_type, hash_function, code_length, api_hostname, clientid, client_secret)
226+
parameter.update({'mfaid': current_mfa['mfaid']})
227+
if (method_type == 'totp'):
228+
current_mfa = zabbix_utils.helper_normalize_data(
229+
current_mfa, del_keys=["api_hostname", "clientid"]
230+
)[0]
231+
difference = {}
232+
zabbix_utils.helper_compare_dictionaries(parameter, current_mfa, difference)
233+
if (difference == {}):
234+
self._module.exit_json(changed=False)
235+
236+
if self._module.check_mode:
237+
self._module.exit_json(changed=True)
238+
self._zapi.mfa.update(parameter)
239+
self._module.exit_json(
240+
changed=True, msg="Successfully updated MFA method."
241+
)
242+
except Exception as e:
243+
self._module.fail_json(
244+
msg="Failed to update MFA method: %s" % e
245+
)
246+
247+
248+
def main():
249+
"""Main ansible module function"""
250+
251+
argument_spec = zabbix_utils.zabbix_common_argument_spec()
252+
argument_spec.update(
253+
dict(
254+
name=dict(type="str", required=True),
255+
method_type=dict(
256+
type="str",
257+
choices=[
258+
"totp",
259+
"duo_universal_prompt"
260+
],
261+
),
262+
hash_function=dict(
263+
type="str",
264+
choices=[
265+
"sha-1",
266+
"sha-256",
267+
"sha-512"
268+
],
269+
),
270+
code_length=dict(
271+
type="int",
272+
choices=[6, 8],
273+
),
274+
api_hostname=dict(type="str"),
275+
clientid=dict(type="str"),
276+
client_secret=dict(type="str", no_log=True),
277+
state=dict(
278+
type="str",
279+
default="present",
280+
choices=["present", "absent"]
281+
)
282+
)
283+
)
284+
285+
module = AnsibleModule(
286+
argument_spec=argument_spec,
287+
required_if=[
288+
[
289+
"method_type",
290+
"totp",
291+
[
292+
"hash_function",
293+
"code_length"
294+
]
295+
],
296+
[
297+
"method_type",
298+
"duo_universal_prompt",
299+
[
300+
"api_hostname",
301+
"clientid",
302+
"client_secret"
303+
]
304+
]
305+
],
306+
mutually_exclusive=[
307+
('hash_function', 'api_hostname')
308+
],
309+
required_by={
310+
'hash_function': 'method_type',
311+
'code_length': 'method_type',
312+
'api_hostname': 'method_type',
313+
'clientid': 'method_type',
314+
'client_secret': 'method_type'
315+
},
316+
supports_check_mode=True,
317+
)
318+
319+
name = module.params["name"]
320+
method_type = module.params["method_type"]
321+
hash_function = module.params["hash_function"]
322+
code_length = module.params["code_length"]
323+
api_hostname = module.params["api_hostname"]
324+
clientid = module.params["clientid"]
325+
client_secret = module.params["client_secret"]
326+
state = module.params["state"]
327+
328+
mfa_class_obj = MFA(module)
329+
mfa = mfa_class_obj.get_mfa(name)
330+
331+
if state == "absent":
332+
if mfa:
333+
mfa_class_obj.delete_mfa(mfa)
334+
else:
335+
module.exit_json(changed=False)
336+
else:
337+
if mfa:
338+
mfa_class_obj.update_mfa(mfa, name, method_type, hash_function, code_length, api_hostname, clientid, client_secret)
339+
else:
340+
mfa_class_obj.create_mfa(name, method_type, hash_function, code_length, api_hostname, clientid, client_secret)
341+
342+
343+
if __name__ == "__main__":
344+
main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
dependencies:
3+
- setup_zabbix

0 commit comments

Comments
 (0)