summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/ansible.cfg1
-rw-r--r--examples/plugin_filters.yml6
-rw-r--r--lib/ansible/config/base.yml10
-rw-r--r--lib/ansible/parsing/utils/yaml.py4
-rw-r--r--lib/ansible/plugins/loader.py70
-rw-r--r--test/integration/targets/plugin_filtering/aliases1
-rw-r--r--test/integration/targets/plugin_filtering/copy.yml10
-rw-r--r--test/integration/targets/plugin_filtering/filter_lookup.ini4
-rw-r--r--test/integration/targets/plugin_filtering/filter_lookup.yml6
-rw-r--r--test/integration/targets/plugin_filtering/filter_modules.ini4
-rw-r--r--test/integration/targets/plugin_filtering/filter_modules.yml9
-rw-r--r--test/integration/targets/plugin_filtering/filter_ping.ini4
-rw-r--r--test/integration/targets/plugin_filtering/filter_ping.yml5
-rw-r--r--test/integration/targets/plugin_filtering/filter_stat.ini4
-rw-r--r--test/integration/targets/plugin_filtering/filter_stat.yml5
-rw-r--r--test/integration/targets/plugin_filtering/lookup.yml14
-rw-r--r--test/integration/targets/plugin_filtering/no_filters.ini4
-rw-r--r--test/integration/targets/plugin_filtering/pause.yml6
-rw-r--r--test/integration/targets/plugin_filtering/ping.yml6
-rwxr-xr-xtest/integration/targets/plugin_filtering/runme.sh128
-rw-r--r--test/integration/targets/plugin_filtering/stat.yml6
-rw-r--r--test/integration/targets/plugin_filtering/tempfile.yml9
22 files changed, 312 insertions, 4 deletions
diff --git a/examples/ansible.cfg b/examples/ansible.cfg
index 10983b4dc7..fde7257163 100644
--- a/examples/ansible.cfg
+++ b/examples/ansible.cfg
@@ -16,6 +16,7 @@
#module_utils = /usr/share/my_module_utils/
#remote_tmp = ~/.ansible/tmp
#local_tmp = ~/.ansible/tmp
+#plugin_filters_cfg = /etc/ansible/plugin_filters.yml
#forks = 5
#poll_interval = 15
#sudo_user = root
diff --git a/examples/plugin_filters.yml b/examples/plugin_filters.yml
new file mode 100644
index 0000000000..b089a4d1f8
--- /dev/null
+++ b/examples/plugin_filters.yml
@@ -0,0 +1,6 @@
+---
+filter_version: '1.0'
+module_blacklist:
+ # List the modules to blacklist here
+ #- easy_install
+ #- s3
diff --git a/lib/ansible/config/base.yml b/lib/ansible/config/base.yml
index 7f61046a96..8f791eeac2 100644
--- a/lib/ansible/config/base.yml
+++ b/lib/ansible/config/base.yml
@@ -1387,6 +1387,16 @@ PLAYBOOK_VARS_ROOT:
ini:
- {key: playbook_vars_root, section: defaults}
choices: [ top, bottom, all ]
+PLUGIN_FILTERS_CFG:
+ name: Config file for limiting valid plugins
+ default: null
+ version_added: "2.5.0"
+ description:
+ - "A path to configuration for filtering which plugins installed on the system are allowed to be used"
+ - " The default is /etc/ansible/plugin_filters.yml"
+ ini:
+ - key: plugin_filters_cfg
+ section: default
RETRY_FILES_ENABLED:
name: Retry files
default: True
diff --git a/lib/ansible/parsing/utils/yaml.py b/lib/ansible/parsing/utils/yaml.py
index de36928c02..82a3d9d6bc 100644
--- a/lib/ansible/parsing/utils/yaml.py
+++ b/lib/ansible/parsing/utils/yaml.py
@@ -54,7 +54,7 @@ def _safe_load(stream, file_name=None, vault_secrets=None):
pass # older versions of yaml don't have dispose function, ignore
-def from_yaml(data, file_name='<string>', show_content=True):
+def from_yaml(data, file_name='<string>', show_content=True, vault_secrets=None):
'''
Creates a python datastructure from the given data, which can be either
a JSON or YAML string.
@@ -80,7 +80,7 @@ def from_yaml(data, file_name='<string>', show_content=True):
except Exception:
# must not be JSON, let the rest try
try:
- new_data = _safe_load(in_data, file_name=file_name)
+ new_data = _safe_load(in_data, file_name=file_name, vault_secrets=vault_secrets)
except YAMLError as yaml_exc:
_handle_error(yaml_exc, file_name, show_content)
diff --git a/lib/ansible/plugins/loader.py b/lib/ansible/plugins/loader.py
index d3602ebdfb..43a6564562 100644
--- a/lib/ansible/plugins/loader.py
+++ b/lib/ansible/plugins/loader.py
@@ -17,8 +17,10 @@ import warnings
from collections import defaultdict
from ansible import constants as C
-from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
+from ansible.errors import AnsibleError
from ansible.module_utils._text import to_text
+from ansible.parsing.utils.yaml import from_yaml
+from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
from ansible.utils.plugin_docs import get_docstring
try:
@@ -235,6 +237,10 @@ class PluginLoader:
def find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False):
''' Find a plugin named name '''
+ global _PLUGIN_FILTERS
+ if name in _PLUGIN_FILTERS[self.package]:
+ return None
+
if mod_type:
suffix = mod_type
elif self.class_name:
@@ -405,6 +411,8 @@ class PluginLoader:
def all(self, *args, **kwargs):
''' instantiates all plugins with the same arguments '''
+ global _PLUGIN_FILTERS
+
path_only = kwargs.pop('path_only', False)
class_only = kwargs.pop('class_only', False)
all_matches = []
@@ -416,7 +424,7 @@ class PluginLoader:
for path in sorted(all_matches, key=os.path.basename):
name = os.path.basename(os.path.splitext(path)[0])
- if '__init__' in name:
+ if '__init__' in name or name in _PLUGIN_FILTERS[self.package]:
continue
if path_only:
@@ -462,6 +470,63 @@ class PluginLoader:
self._update_object(obj, name, path)
yield obj
+
+def _load_plugin_filter():
+ filters = defaultdict(frozenset)
+
+ if C.PLUGIN_FILTERS_CFG is None:
+ filter_cfg = '/etc/ansible/plugin_filters.yml'
+ user_set = False
+ else:
+ filter_cfg = C.PLUGIN_FILTERS_CFG
+ user_set = True
+
+ if os.path.exists(filter_cfg):
+ with open(filter_cfg, 'rb') as f:
+ try:
+ filter_data = from_yaml(f.read())
+ except Exception as e:
+ display.warning(u'The plugin filter file, {0} was not parsable.'
+ u' Skipping: {1}'.format(filter_cfg, to_text(e)))
+ return filters
+
+ try:
+ version = filter_data['filter_version']
+ except KeyError:
+ display.warning(u'The plugin filter file, {0} was invalid.'
+ u' Skipping.'.format(filter_cfg))
+ return filters
+
+ # Try to convert for people specifying version as a float instead of string
+ version = to_text(version)
+ version = version.strip()
+
+ if version == u'1.0':
+ # Modules and action plugins share the same blacklist since the difference between the
+ # two isn't visible to the users
+ filters['ansible.modules'] = frozenset(filter_data['module_blacklist'])
+ filters['ansible.plugins.action'] = filters['ansible.modules']
+ else:
+ display.warning(u'The plugin filter file, {0} was a version not recognized by this'
+ u' version of Ansible. Skipping.')
+ else:
+ if user_set:
+ display.warning(u'The plugin filter file, {0} does not exist.'
+ u' Skipping.'.format(filter_cfg))
+
+ # Specialcase the stat module as Ansible can run very few things if stat is blacklisted.
+ if 'stat' in filters['ansible.modules']:
+ raise AnsibleError('The stat module was specified in the module blacklist file, {0}, but'
+ ' Ansible will not function without the stat module. Please remove stat'
+ ' from the blacklist.'.format(filter_cfg))
+ return filters
+
+
+# TODO: All of the following is initialization code It should be moved inside of an initialization
+# function which is called at some point early in the ansible and ansible-playbook CLI startup.
+
+_PLUGIN_FILTERS = _load_plugin_filter()
+
# doc fragments first
fragment_loader = PluginLoader(
'ModuleDocFragment',
@@ -470,6 +535,7 @@ fragment_loader = PluginLoader(
'',
)
+
action_loader = PluginLoader(
'ActionModule',
'ansible.plugins.action',
diff --git a/test/integration/targets/plugin_filtering/aliases b/test/integration/targets/plugin_filtering/aliases
new file mode 100644
index 0000000000..79d8b9285e
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/aliases
@@ -0,0 +1 @@
+posix/ci/group3
diff --git a/test/integration/targets/plugin_filtering/copy.yml b/test/integration/targets/plugin_filtering/copy.yml
new file mode 100644
index 0000000000..083386a1de
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/copy.yml
@@ -0,0 +1,10 @@
+---
+- hosts: testhost
+ gather_facts: False
+ tasks:
+ - copy:
+ content: 'Testing 1... 2... 3...'
+ dest: ./testing.txt
+ - file:
+ state: absent
+ path: ./testing.txt
diff --git a/test/integration/targets/plugin_filtering/filter_lookup.ini b/test/integration/targets/plugin_filtering/filter_lookup.ini
new file mode 100644
index 0000000000..17e58e334d
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_lookup.ini
@@ -0,0 +1,4 @@
+[default]
+retry_files_enabled = False
+plugin_filters_cfg = ./filter_lookup.yml
+
diff --git a/test/integration/targets/plugin_filtering/filter_lookup.yml b/test/integration/targets/plugin_filtering/filter_lookup.yml
new file mode 100644
index 0000000000..694ebfcb72
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_lookup.yml
@@ -0,0 +1,6 @@
+---
+filter_version: 1.0
+module_blacklist:
+ # Specify the name of a lookup plugin here. This should have no effect as
+ # this is only for filtering modules
+ - list
diff --git a/test/integration/targets/plugin_filtering/filter_modules.ini b/test/integration/targets/plugin_filtering/filter_modules.ini
new file mode 100644
index 0000000000..ab39beddbc
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_modules.ini
@@ -0,0 +1,4 @@
+[default]
+retry_files_enabled = False
+plugin_filters_cfg = ./filter_modules.yml
+
diff --git a/test/integration/targets/plugin_filtering/filter_modules.yml b/test/integration/targets/plugin_filtering/filter_modules.yml
new file mode 100644
index 0000000000..6cffa67614
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_modules.yml
@@ -0,0 +1,9 @@
+---
+filter_version: 1.0
+module_blacklist:
+ # A pure action plugin
+ - pause
+ # A hybrid action plugin with module
+ - copy
+ # A pure module
+ - tempfile
diff --git a/test/integration/targets/plugin_filtering/filter_ping.ini b/test/integration/targets/plugin_filtering/filter_ping.ini
new file mode 100644
index 0000000000..aabbde4566
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_ping.ini
@@ -0,0 +1,4 @@
+[default]
+retry_files_enabled = False
+plugin_filters_cfg = ./filter_ping.yml
+
diff --git a/test/integration/targets/plugin_filtering/filter_ping.yml b/test/integration/targets/plugin_filtering/filter_ping.yml
new file mode 100644
index 0000000000..08e56f2439
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_ping.yml
@@ -0,0 +1,5 @@
+---
+filter_version: 1.0
+module_blacklist:
+ # Ping is special
+ - ping
diff --git a/test/integration/targets/plugin_filtering/filter_stat.ini b/test/integration/targets/plugin_filtering/filter_stat.ini
new file mode 100644
index 0000000000..13a103ddb3
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_stat.ini
@@ -0,0 +1,4 @@
+[default]
+retry_files_enabled = False
+plugin_filters_cfg = ./filter_stat.yml
+
diff --git a/test/integration/targets/plugin_filtering/filter_stat.yml b/test/integration/targets/plugin_filtering/filter_stat.yml
new file mode 100644
index 0000000000..c1ce42efd7
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/filter_stat.yml
@@ -0,0 +1,5 @@
+---
+filter_version: 1.0
+module_blacklist:
+ # Stat is special
+ - stat
diff --git a/test/integration/targets/plugin_filtering/lookup.yml b/test/integration/targets/plugin_filtering/lookup.yml
new file mode 100644
index 0000000000..de6d1b485b
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/lookup.yml
@@ -0,0 +1,14 @@
+---
+- hosts: testhost
+ gather_facts: False
+ vars:
+ data:
+ - one
+ - two
+ tasks:
+ - debug:
+ msg: '{{ lookup("list", data) }}'
+
+ - debug:
+ msg: '{{ item }}'
+ with_list: '{{ data }}'
diff --git a/test/integration/targets/plugin_filtering/no_filters.ini b/test/integration/targets/plugin_filtering/no_filters.ini
new file mode 100644
index 0000000000..4b42c8c465
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/no_filters.ini
@@ -0,0 +1,4 @@
+[default]
+retry_files_enabled = False
+plugin_filters_cfg = ./empty.yml
+
diff --git a/test/integration/targets/plugin_filtering/pause.yml b/test/integration/targets/plugin_filtering/pause.yml
new file mode 100644
index 0000000000..e2c1ef9c0f
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/pause.yml
@@ -0,0 +1,6 @@
+---
+- hosts: testhost
+ gather_facts: False
+ tasks:
+ - pause:
+ seconds: 1
diff --git a/test/integration/targets/plugin_filtering/ping.yml b/test/integration/targets/plugin_filtering/ping.yml
new file mode 100644
index 0000000000..9e2214b039
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/ping.yml
@@ -0,0 +1,6 @@
+---
+- hosts: testhost
+ gather_facts: False
+ tasks:
+ - ping:
+ data: 'Testing 1... 2... 3...'
diff --git a/test/integration/targets/plugin_filtering/runme.sh b/test/integration/targets/plugin_filtering/runme.sh
new file mode 100755
index 0000000000..d1ef407879
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/runme.sh
@@ -0,0 +1,128 @@
+#!/usr/bin/env bash
+
+set -ux
+
+#
+# Check that with no filters set, all of these modules run as expected
+#
+ANSIBLE_CONFIG=no_filters.ini ansible-playbook copy.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run copy with no filters applied"
+ exit 1
+fi
+ANSIBLE_CONFIG=no_filters.ini ansible-playbook pause.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run pause with no filters applied"
+ exit 1
+fi
+ANSIBLE_CONFIG=no_filters.ini ansible-playbook tempfile.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run tempfile with no filters applied"
+ exit 1
+fi
+
+#
+# Check that with these modules filtered out, all of these modules fail to be found
+#
+ANSIBLE_CONFIG=filter_modules.ini ansible-playbook copy.yml -i ../../inventory -v "$@"
+if test $? = 0 ; then
+ echo "### Failed to prevent copy from running"
+ exit 1
+else
+ echo "### Copy was prevented from running as expected"
+fi
+ANSIBLE_CONFIG=filter_modules.ini ansible-playbook pause.yml -i ../../inventory -v "$@"
+if test $? = 0 ; then
+ echo "### Failed to prevent pause from running"
+ exit 1
+else
+ echo "### pause was prevented from running as expected"
+fi
+ANSIBLE_CONFIG=filter_modules.ini ansible-playbook tempfile.yml -i ../../inventory -v "$@"
+if test $? = 0 ; then
+ echo "### Failed to prevent tempfile from running"
+ exit 1
+else
+ echo "### tempfile was prevented from running as expected"
+fi
+
+#
+# ping is a special module as we test for its existence. Check it specially
+#
+
+# Check that ping runs with no filter
+ANSIBLE_CONFIG=no_filters.ini ansible-playbook ping.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run ping with no filters applied"
+ exit 1
+fi
+
+# Check that other modules run with ping filtered
+ANSIBLE_CONFIG=filter_ping.ini ansible-playbook copy.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run copy when a filter was applied to ping"
+ exit 1
+fi
+# Check that ping fails to run when it is filtered
+ANSIBLE_CONFIG=filter_ping.ini ansible-playbook ping.yml -i ../../inventory -v "$@"
+if test $? = 0 ; then
+ echo "### Failed to prevent ping from running"
+ exit 1
+else
+ echo "### Ping was prevented from running as expected"
+fi
+
+#
+# Check that specifying a lookup plugin in the filter has no effect
+#
+
+ANSIBLE_CONFIG=filter_lookup.ini ansible-playbook lookup.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to use a lookup plugin when it is incorrectly specified in the *module* blacklist"
+ exit 1
+fi
+
+#
+# stat is a special module as we use it to run nearly every other module. Check it specially
+#
+
+# Check that stat runs with no filter
+ANSIBLE_CONFIG=no_filters.ini ansible-playbook stat.yml -i ../../inventory -vvv "$@"
+if test $? != 0 ; then
+ echo "### Failed to run stat with no filters applied"
+ exit 1
+fi
+
+# Check that running another module when stat is filtered gives us our custom error message
+ANSIBLE_CONFIG=filter_stat.ini
+export ANSIBLE_CONFIG
+CAPTURE=$(ansible-playbook copy.yml -i ../../inventory -vvv "$@" 2>&1)
+if test $? = 0 ; then
+ echo "### Copy ran even though stat is in the module blacklist"
+ exit 1
+else
+ echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.'
+ if test $? != 0 ; then
+ echo "### Stat did not give us our custom error message"
+ exit 1
+ fi
+ echo "### Filtering stat failed with our custom error message as expected"
+fi
+unset ANSIBLE_CONFIG
+
+# Check that running stat when stat is filtered gives our custom error message
+ANSIBLE_CONFIG=filter_stat.ini
+export ANSIBLE_CONFIG
+CAPTURE=$(ansible-playbook stat.yml -i ../../inventory -vvv "$@" 2>&1)
+if test $? = 0 ; then
+ echo "### Stat ran even though it is in the module blacklist"
+ exit 1
+else
+ echo "$CAPTURE" | grep 'The stat module was specified in the module blacklist file,.*, but Ansible will not function without the stat module. Please remove stat from the blacklist.'
+ if test $? != 0 ; then
+ echo "### Stat did not give us our custom error message"
+ exit 1
+ fi
+ echo "### Filtering stat failed with our custom error message as expected"
+fi
+unset ANSIBLE_CONFIG
diff --git a/test/integration/targets/plugin_filtering/stat.yml b/test/integration/targets/plugin_filtering/stat.yml
new file mode 100644
index 0000000000..4f24baaea2
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/stat.yml
@@ -0,0 +1,6 @@
+---
+- hosts: testhost
+ gather_facts: False
+ tasks:
+ - stat:
+ path: '/'
diff --git a/test/integration/targets/plugin_filtering/tempfile.yml b/test/integration/targets/plugin_filtering/tempfile.yml
new file mode 100644
index 0000000000..06463547de
--- /dev/null
+++ b/test/integration/targets/plugin_filtering/tempfile.yml
@@ -0,0 +1,9 @@
+---
+- hosts: testhost
+ gather_facts: False
+ tasks:
+ - tempfile:
+ register: temp_result
+ - file:
+ state: absent
+ path: '{{ temp_result["path"] }}'