New module - meraki_malware (#56929)

* Initial commit for meraki_malware module
- Allows for manipulation of malware configuration

* Add full documentation and improve code coverage

* Add diff support

* Type change

* Sanity check fixes

* Convert org_id from str to int for consistency

* Sanity fixes again

* Fix argument type errors

* Remove ignore items for some Meraki modules so shippable is happy
This commit is contained in:
Kevin Breit 2019-06-05 09:36:39 -05:00 committed by Nathaniel Case
parent 8bcf9b5734
commit 919f70c357
8 changed files with 527 additions and 11 deletions

View file

@ -182,8 +182,6 @@ def main():
# the module
argument_spec = meraki_argument_spec()
argument_spec.update(state=dict(type='str', choices=['absent', 'query', 'present'], default='query'),
org_name=dict(type='str', aliases=['organization']),
org_id=dict(type='int'),
config_template=dict(type='str', aliases=['name']),
net_name=dict(type='str'),
net_id=dict(type='str'),

View file

@ -0,0 +1,276 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2019, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
# 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
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = r'''
---
module: meraki_malware
short_description: Manage Malware Protection in the Meraki cloud
version_added: "2.9"
description:
- Fully configure malware protection in a Meraki environment.
notes:
- Some of the options are likely only used for developers within Meraki.
options:
state:
description:
- Specifies whether object should be queried, created/modified, or removed.
choices: [absent, present, query]
default: query
type: str
net_name:
description:
- Name of network which configuration is applied to.
aliases: [network]
type: str
net_id:
description:
- ID of network which configuration is applied to.
type: str
allowed_urls:
description:
- List of URLs to whitelist.
suboptions:
url:
description:
- URL string to allow.
type: str
comment:
description:
- Human readable information about URL.
type: str
allowed_files:
description:
- List of files to whitelist.
suboptions:
sha256:
description:
- 256-bit hash of file.
type: str
aliases: [ hash ]
comment:
description:
- Human readable information about file.
type: str
mode:
description:
- Enabled or disabled state of malware protection.
choices: [disabled, enabled]
author:
- Kevin Breit (@kbreit)
extends_documentation_fragment: meraki
'''
EXAMPLES = r'''
- name: Enable malware protection
meraki_malware:
auth_key: abc123
state: present
org_name: YourOrg
net_name: YourNet
mode: enabled
delegate_to: localhost
- name: Set whitelisted url
meraki_malware:
auth_key: abc123
state: present
org_name: YourOrg
net_name: YourNet
mode: enabled
allowed_urls:
- url: www.google.com
comment: Google
delegate_to: localhost
- name: Set whitelisted file
meraki_malware:
auth_key: abc123
state: present
org_name: YourOrg
net_name: YourNet
mode: enabled
allowed_files:
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment: random zip
delegate_to: localhost
- name: Get malware settings
meraki_malware:
auth_key: abc123
state: query
org_name: YourNet
net_name: YourOrg
delegate_to: localhost
'''
RETURN = r'''
data:
description: List of administrators.
returned: success
type: complex
contains:
mode:
description: Mode to enable or disable malware scanning.
returned: success
type: str
sample: enabled
allowed_files:
description: List of files which are whitelisted.
returned: success
type: complex
contains:
sha256:
description: sha256 hash of whitelisted file.
returned: success
type: str
sample: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment:
description: Comment about the whitelisted entity
returned: success
type: str
sample: TPS report
allowed_urls:
description: List of URLs which are whitelisted.
returned: success
type: complex
contains:
url:
description: URL of whitelisted site.
returned: success
type: str
sample: site.com
comment:
description: Comment about the whitelisted entity
returned: success
type: str
sample: Corporate HQ
'''
import os
from ansible.module_utils.basic import AnsibleModule, json, env_fallback
from ansible.module_utils._text import to_native
from ansible.module_utils.common.dict_transformations import recursive_diff
from ansible.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec
def main():
# define the available arguments/parameters that a user can pass to
# the module
urls_arg_spec = dict(url=dict(type='str'),
comment=dict(type='str'),
)
files_arg_spec = dict(sha256=dict(type='str', aliases=['hash']),
comment=dict(type='str'),
)
argument_spec = meraki_argument_spec()
argument_spec.update(state=dict(type='str', choices=['absent', 'present', 'query'], default='query'),
net_name=dict(type='str', aliases=['network']),
net_id=dict(type='str'),
mode=dict(type='str', choices=['enabled', 'disabled']),
allowed_urls=dict(type='list', default=None, elements='dict', options=urls_arg_spec),
allowed_files=dict(type='list', default=None, elements='dict', options=files_arg_spec),
)
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
)
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True,
)
meraki = MerakiModule(module, function='malware')
meraki.params['follow_redirects'] = 'all'
query_url = {'malware': '/networks/{net_id}/security/malwareSettings'}
update_url = {'malware': '/networks/{net_id}/security/malwareSettings'}
meraki.url_catalog['get_one'].update(query_url)
meraki.url_catalog['update'] = update_url
org_id = meraki.params['org_id']
if org_id is None:
org_id = meraki.get_org_id(meraki.params['org_name'])
net_id = meraki.params['net_id']
if net_id is None:
nets = meraki.get_nets(org_id=org_id)
net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
# Check for argument completeness
if meraki.params['state'] == 'present':
if meraki.params['allowed_files'] is not None or meraki.params['allowed_urls'] is not None:
if meraki.params['mode'] is None:
meraki.fail_json(msg="mode must be set when allowed_files or allowed_urls is set.")
# Assemble payload
if meraki.params['state'] == 'present':
payload = dict()
if meraki.params['mode'] is not None:
payload['mode'] = meraki.params['mode']
if meraki.params['allowed_urls'] is not None:
payload['allowedUrls'] = meraki.params['allowed_urls']
if meraki.params['allowed_files'] is not None:
payload['allowedFiles'] = meraki.params['allowed_files']
if meraki.params['state'] == 'query':
path = meraki.construct_path('get_one', net_id=net_id)
data = meraki.request(path, method='GET')
if meraki.status == 200:
meraki.result['data'] = data
elif meraki.params['state'] == 'present':
path = meraki.construct_path('get_one', net_id=net_id)
original = meraki.request(path, method='GET')
if meraki.is_update_required(original, payload):
if meraki.module.check_mode is True:
diff = recursive_diff(original, payload)
original.update(payload)
meraki.result['diff'] = {'before': diff[0],
'after': diff[1],
}
meraki.result['data'] = original
meraki.result['changed'] = True
meraki.exit_json(**meraki.result)
path = meraki.construct_path('update', net_id=net_id)
data = meraki.request(path, method='PUT', payload=json.dumps(payload))
if meraki.status == 200:
diff = recursive_diff(original, payload)
meraki.result['diff'] = {'before': diff[0],
'after': diff[1],
}
meraki.result['data'] = data
meraki.result['changed'] = True
else:
meraki.result['data'] = original
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
meraki.exit_json(**meraki.result)
if __name__ == '__main__':
main()

View file

@ -38,6 +38,7 @@ options:
description:
- ID of organization.
aliases: [ id ]
type: str
author:
- Kevin Breit (@kbreit)
extends_documentation_fragment: meraki
@ -124,7 +125,7 @@ def main():
argument_spec.update(clone=dict(type='str'),
state=dict(type='str', choices=['present', 'query'], default='present'),
org_name=dict(type='str', aliases=['name', 'organization']),
org_id=dict(type='int', aliases=['id']),
org_id=dict(type='str', aliases=['id']),
)
# seed the result dict in the object

View file

@ -219,8 +219,6 @@ def main():
# the module
argument_spec = meraki_argument_spec()
argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
org_name=dict(type='str', aliases=['organization']),
org_id=dict(type='int'),
v2c_enabled=dict(type='bool'),
v3_enabled=dict(type='bool'),
v3_auth_mode=dict(type='str', choices=['SHA', 'MD5']),

View file

@ -56,5 +56,5 @@ options:
org_id:
description:
- ID of organization.
type: int
type: str
'''

View file

@ -0,0 +1 @@
unsupported

View file

@ -0,0 +1,247 @@
# Test code for the Meraki VLAN module
# Copyright: (c) 2018, Kevin Breit (@kbreit)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
---
- block:
- name: Test an API key is provided
fail:
msg: Please define an API key
when: auth_key is not defined
- name: Create test network
meraki_network:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
type: appliance
delegate_to: localhost
register: net
- set_fact:
net_id: '{{net.data.id}}'
- name: Enable malware protection with check mode
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
delegate_to: localhost
check_mode: yes
register: get_malware_check
- assert:
that:
- get_malware_check is changed
- get_malware_check.data is defined
- name: Enable malware protection
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
delegate_to: localhost
register: get_malware
- debug:
var: get_malware
- assert:
that:
- get_malware is changed
- get_malware.data.mode is defined
- name: Enable malware protection with idempotency
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
delegate_to: localhost
register: get_malware_idempotent
- debug:
var: get_malware_idempotent
- assert:
that:
- get_malware_idempotent is not changed
- get_malware_idempotent.data is defined
- name: Test error when mode is not set
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
allowed_files:
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment: random zip
delegate_to: localhost
register: test_mode_err
ignore_errors: yes
- assert:
that:
- test_mode_err.msg == "mode must be set when allowed_files or allowed_urls is set."
- name: Set whitelisted file with check mode
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
allowed_files:
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment: random zip
delegate_to: localhost
check_mode: yes
register: set_file_check
- debug:
var:
set_file_check
- assert:
that:
- set_file_check is changed
- set_file_check.data is defined
- name: Set whitelisted file
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_id: '{{net_id}}'
mode: enabled
allowed_files:
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment: random zip
delegate_to: localhost
register: set_file
- debug:
var: set_file
- assert:
that:
- set_file is changed
- set_file.data.mode is defined
- name: Set whitelisted file with idempotency
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
allowed_files:
- sha256: e82c5f7d75004727e1f3b94426b9a11c8bc4c312a9170ac9a73abace40aef503
comment: random zip
delegate_to: localhost
register: set_file_idempotent
- debug:
var: set_file_idempotent
- assert:
that:
- set_file_idempotent is not changed
- set_file_idempotent.data is defined
- name: Set whitelisted url with check mode
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
allowed_urls:
- url: www.google.com
comment: Google
delegate_to: localhost
check_mode: yes
register: set_url_check
- debug:
var:
set_url_check
- assert:
that:
- set_url_check is changed
- set_url_check.data is defined
- name: Set whitelisted url
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
allowed_urls:
- url: www.google.com
comment: Google
delegate_to: localhost
register: set_url
- debug:
var: set_url
- assert:
that:
- set_url is changed
- set_url.data.mode is defined
- name: Set whitelisted url with idempotency
meraki_malware:
auth_key: '{{auth_key}}'
state: present
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
mode: enabled
allowed_urls:
- url: www.google.com
comment: Google
delegate_to: localhost
register: set_url_idempotent
- debug:
var: set_url_idempotent
- assert:
that:
- set_url_idempotent is not changed
- set_url_idempotent.data is defined
- name: Get malware settings
meraki_malware:
auth_key: '{{auth_key}}'
state: query
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
delegate_to: localhost
register: get_malware
- assert:
that:
- get_malware.data is defined
#############################################################################
# Tear down starts here
#############################################################################
always:
- name: Delete test network
meraki_network:
auth_key: '{{auth_key}}'
state: absent
org_name: '{{test_org_name}}'
net_name: '{{test_net_name}} - Malware'
delegate_to: localhost

View file

@ -536,13 +536,8 @@ lib/ansible/modules/network/junos/junos_logging.py E322
lib/ansible/modules/network/junos/junos_rpc.py E326
lib/ansible/modules/network/junos/junos_static_route.py E322
lib/ansible/modules/network/junos/junos_vrf.py E324
lib/ansible/modules/network/meraki/meraki_device.py E325
lib/ansible/modules/network/meraki/meraki_mr_l3_firewall.py E325
lib/ansible/modules/network/meraki/meraki_mx_l3_firewall.py E325
lib/ansible/modules/network/meraki/meraki_network.py E325
lib/ansible/modules/network/meraki/meraki_ssid.py E325
lib/ansible/modules/network/meraki/meraki_switchport.py E325
lib/ansible/modules/network/meraki/meraki_vlan.py E325
lib/ansible/modules/network/netact/netact_cm_command.py E326
lib/ansible/modules/network/netconf/netconf_config.py E326
lib/ansible/modules/network/netscaler/netscaler_cs_action.py E323