File 0044-Add-general-sanitisers.patch of Package salt.4202
From 16ebe554952ee8a2c7c71fd0cdbd2828645f6eb6 Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Wed, 23 Nov 2016 19:07:00 +0100
Subject: [PATCH 44/45] Add general sanitisers
Add filename sanitiser
Add hostname sanitiser
Rename "general.py" to "sanitisers.py"
Add a stub for ID sanitiser (at the moment same as hostname)
Rename for american spelling
Remove trailing dots
Sanitize ID input for key generator
Add test case for sanitizers
Fix imports: builtins was deprecated
Sanitize only non-None values
---
salt/utils/sanitizers.py | 61 +++++++++++++++++++++++++++++++++++++
salt/wheel/key.py | 5 +++
tests/unit/utils/sanitizers_test.py | 57 ++++++++++++++++++++++++++++++++++
3 files changed, 123 insertions(+)
create mode 100644 salt/utils/sanitizers.py
create mode 100644 tests/unit/utils/sanitizers_test.py
diff --git a/salt/utils/sanitizers.py b/salt/utils/sanitizers.py
new file mode 100644
index 0000000000..a80d8e22eb
--- /dev/null
+++ b/salt/utils/sanitizers.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2016 SUSE LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+import os.path
+from salt.ext.six import text_type as text
+
+from salt.exceptions import CommandExecutionError
+
+
+class InputSanitizer(object):
+ @staticmethod
+ def trim(value):
+ '''
+ Raise an exception if value is empty. Otherwise strip it down.
+ :param value:
+ :return:
+ '''
+ value = (value or '').strip()
+ if not value:
+ raise CommandExecutionError("Empty value during sanitation")
+
+ return text(value)
+
+ @staticmethod
+ def filename(value):
+ '''
+ Remove everything that would affect paths in the filename
+
+ :param value:
+ :return:
+ '''
+ return re.sub('[^a-zA-Z0-9.-_ ]', '', os.path.basename(InputSanitizer.trim(value)))
+
+ @staticmethod
+ def hostname(value):
+ '''
+ Clean value for RFC1123.
+
+ :param value:
+ :return:
+ '''
+ return re.sub(r'[^a-zA-Z0-9.-]', '', InputSanitizer.trim(value)).strip('.')
+
+ id = hostname
+
+
+clean = InputSanitizer()
diff --git a/salt/wheel/key.py b/salt/wheel/key.py
index 6b69e150ad..8730ca783d 100644
--- a/salt/wheel/key.py
+++ b/salt/wheel/key.py
@@ -11,6 +11,8 @@ import hashlib
# Import salt libs
import salt.key
import salt.crypt
+from salt.utils.sanitizers import clean
+
__func_alias__ = {
'list_': 'list'
@@ -117,6 +119,8 @@ def gen(id_=None, keysize=2048):
'''
if id_ is None:
id_ = hashlib.sha512(os.urandom(32)).hexdigest()
+ else:
+ id_ = clean.id(id_)
ret = {'priv': '',
'pub': ''}
priv = salt.crypt.gen_keys(__opts__['pki_dir'], id_, keysize)
@@ -135,6 +139,7 @@ def gen_accept(id_, keysize=2048, force=False):
Generate a key pair then accept the public key. This function returns the
key pair in a dict, only the public key is preserved on the master.
'''
+ id_ = clean.id(id_)
ret = gen(id_, keysize)
acc_path = os.path.join(__opts__['pki_dir'], 'minions', id_)
if os.path.isfile(acc_path) and not force:
diff --git a/tests/unit/utils/sanitizers_test.py b/tests/unit/utils/sanitizers_test.py
new file mode 100644
index 0000000000..a80d80d4cf
--- /dev/null
+++ b/tests/unit/utils/sanitizers_test.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+
+# Import python libs
+from __future__ import absolute_import
+from salt.ext.six import text_type as text
+
+# Import Salt Libs
+from salt.utils.sanitizers import clean
+
+# Import Salt Testing Libs
+from salttesting import TestCase, skipIf
+from salttesting.mock import NO_MOCK, NO_MOCK_REASON
+from salttesting.helpers import ensure_in_syspath
+
+ensure_in_syspath('../../')
+
+
+@skipIf(NO_MOCK, NO_MOCK_REASON)
+class SanitizersTestCase(TestCase):
+ '''
+ TestCase for sanitizers
+ '''
+ def test_sanitized_trim(self):
+ '''
+ Test sanitized input for trimming
+ '''
+ value = u' sample '
+ response = clean.trim(value)
+ assert response == 'sample'
+ assert type(response) == text
+
+ def test_sanitized_filename(self):
+ '''
+ Test sanitized input for filename
+ '''
+ value = '/absolute/path/to/the/file.txt'
+ response = clean.filename(value)
+ assert response == 'file.txt'
+
+ value = '../relative/path/to/the/file.txt'
+ response = clean.filename(value)
+ assert response == 'file.txt'
+
+ def test_sanitized_hostname(self):
+ '''
+ Test sanitized input for hostname (id)
+ '''
+ value = ' ../ ../some/dubious/hostname '
+ response = clean.hostname(value)
+ assert response == 'somedubioushostname'
+
+ test_sanitized_id = test_sanitized_hostname
+
+
+if __name__ == '__main__':
+ from integration import run_tests
+ run_tests(ArgsTestCase, needs_daemon=False)
--
2.11.0