From e5939fffdd579c731f4252a33692b40188d7ed2b Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Thu, 30 Oct 2014 11:03:44 +0100 Subject: [PATCH 1/8] Create pam_limits.py The pam_limits module modify PAM limits, default in /etc/security/limits.conf. For the full documentation, see man limits.conf(5). --- system/pam_limits.py | 222 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 system/pam_limits.py diff --git a/system/pam_limits.py b/system/pam_limits.py new file mode 100644 index 00000000000..0c492699eba --- /dev/null +++ b/system/pam_limits.py @@ -0,0 +1,222 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2014, Sebastien Rohaut +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +import os +import os.path +import shutil +import re + +DOCUMENTATION = ''' +--- +module: pam_limits +version_added: "historical" +short_description: Modify Linux PAM limits +description: + - The M(pam_limits) module modify PAM limits, default in /etc/security/limits.conf. + For the full documentation, see man limits.conf(5). +options: + domain: + description: + - A username, @groupname, wildcard, uid/gid range. + required: true + default: null + limit_type: + description: + - Limit type : hard or soft. + required: true + choices: [ "hard", "soft" ] + default: null + limit_item: + description: + - The limit to be set : core, data, nofile, cpu, etc. + required: true + choices: [ "core", "data", "fsize", "memlock", "nofile", "rss", "stack", "cpu", "nproc", "as", "maxlogins", "maxsyslogins", "priority", "locks", "sigpending", "msgqueue", "nice", "rtprio", "chroot" ] + default: null + value: + description: + - The value of the limit. + required: true + default: null + backup: + description: + - Create a backup file including the timestamp information so you can get + the original file back if you somehow clobbered it incorrectly. + required: false + choices: [ "yes", "no" ] + default: "no" + use_min: + description: + - If set to C(yes), the minimal value will be used or conserved. + If the specified value is inferior to the value in the file, file content is replaced with the new value, + else content is not modified. + required: false + choices: [ "yes", "no" ] + default: "no" + use_max: + description: + - If set to C(yes), the maximal value will be used or conserved. + If the specified value is superior to the value in the file, file content is replaced with the new value, + else content is not modified. + required: false + choices: [ "yes", "no" ] + default: "no" + dest: + description: + - Modify the limits.conf path. + required: false + default: "/etc/security/limits.conf" +''' + +EXAMPLES = ''' +# Add or modify limits for the user joe +- pam_limits: domain=joe limit_type=soft limit_item=nofile value=64000 + +# Add or modify limits for the user joe. Keep or set the maximal value +- pam_limits: domain=joe limit_type=soft limit_item=nofile value=1000000 +''' + +def main(): + + pam_items = [ 'core', 'data', 'fsize', 'memlock', 'nofile', 'rss', 'stack', 'cpu', 'nproc', 'as', 'maxlogins', 'maxsyslogins', 'priority', 'locks', 'sigpending', 'msgqueue', 'nice', 'rtprio', 'chroot' ] + + pam_types = [ 'soft', 'hard' ] + + limits_conf = '/home/slyce/limits.conf' + + module = AnsibleModule( + # not checking because of daisy chain to file module + argument_spec = dict( + domain = dict(required=True, type='str'), + limit_type = dict(required=True, type='str', choices=pam_types), + limit_item = dict(required=True, type='str', choices=pam_items), + value = dict(required=True, type='int'), + use_max = dict(default=False, type='bool'), + use_min = dict(default=False, type='bool'), + backup = dict(default=False, type='bool'), + dest = dict(default=limits_conf, type='str') + ) + ) + + domain = module.params['domain'] + limit_type = module.params['limit_type'] + limit_item = module.params['limit_item'] + value = module.params['value'] + use_max = module.params['use_max'] + use_min = module.params['use_min'] + backup = module.params['backup'] + limits_conf = module.params['dest'] + + changed = False + + if os.path.isfile(limits_conf): + if not os.access(limits_conf, os.W_OK): + module.fail_json(msg="%s is not writable. Use sudo" % (limits_conf) ) + else: + module.fail_json(msg="%s is not visible (check presence, access rights, use sudo)" % (limits_conf) ) + + # Backup + if backup: + backup_file = module.backup_local(limits_conf) + + space_pattern = re.compile(r'\s+') + + message = '' + f = open (limits_conf, 'r') + # Tempfile + nf = tempfile.NamedTemporaryFile(delete = False) + + found = False + new_value = value + + for line in f: + if line.startswith('#'): + nf.write(line) + continue + + newline = re.sub(space_pattern, ' ', line).strip() + if not newline: + nf.write(line) + continue + + line_fields = newline.split(' ') + + if len(line_fields) != 4: + nf.write(line) + continue + + line_domain = line_fields[0] + line_type = line_fields[1] + line_item = line_fields[2] + actual_value = int(line_fields[3]) + + # Found the line + if line_domain == domain and line_type == limit_type and line_item == limit_item: + found = True + if value == actual_value: + message = line + nf.write(line) + continue + + if use_max: + new_value = max(value, actual_value) + + if use_min: + new_value = min(value,actual_value) + + # Change line only if value has changed + if new_value != actual_value: + changed = True + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + "\n" + message = new_limit + nf.write(new_limit) + else: + message = line + nf.write(line) + else: + nf.write(line) + + if not found: + changed = True + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + "\n" + message = new_limit + nf.write(new_limit) + + f.close() + nf.close() + + # Copy tempfile to newfile + shutil.copy(nf.name, f.name) + + # delete tempfile + os.unlink(nf.name) + + res_args = dict( + changed = changed, msg = message + ) + + if backup: + res_args['backup_file'] = backup_file + + module.exit_json(**res_args) + + +# import module snippets +from ansible.module_utils.basic import * +main() From 8122edd727483aa2a80744dc9be9d7b9cb1b7e05 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Thu, 30 Oct 2014 11:07:42 +0100 Subject: [PATCH 2/8] Update pam_limits.py Tabs... --- system/pam_limits.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index 0c492699eba..ae0e181d6e1 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -138,12 +138,12 @@ def main(): space_pattern = re.compile(r'\s+') message = '' - f = open (limits_conf, 'r') + f = open (limits_conf, 'r') # Tempfile nf = tempfile.NamedTemporaryFile(delete = False) found = False - new_value = value + new_value = value for line in f: if line.startswith('#'): From b845c0ef56ac94bfd6d59912ba5a8628880093c4 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Thu, 30 Oct 2014 11:10:02 +0100 Subject: [PATCH 3/8] Update pam_limits.py --- system/pam_limits.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/pam_limits.py b/system/pam_limits.py index ae0e181d6e1..338c00e03d5 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -139,6 +139,7 @@ def main(): message = '' f = open (limits_conf, 'r') + # Tempfile nf = tempfile.NamedTemporaryFile(delete = False) From c8fbac0ff21d515b417bbde9572df018fa5be867 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Thu, 30 Oct 2014 11:10:49 +0100 Subject: [PATCH 4/8] Update pam_limits.py --- system/pam_limits.py | 1 - 1 file changed, 1 deletion(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index 338c00e03d5..ae0e181d6e1 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -139,7 +139,6 @@ def main(): message = '' f = open (limits_conf, 'r') - # Tempfile nf = tempfile.NamedTemporaryFile(delete = False) From 722d810ef22df1bd794e1ef01a53ad7995f28c1d Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Thu, 30 Oct 2014 11:13:17 +0100 Subject: [PATCH 5/8] Update pam_limits.py Change path --- system/pam_limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index ae0e181d6e1..fde70abd800 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -98,7 +98,7 @@ def main(): pam_types = [ 'soft', 'hard' ] - limits_conf = '/home/slyce/limits.conf' + limits_conf = '/etc/security/limits.conf' module = AnsibleModule( # not checking because of daisy chain to file module From 4c22ee75b15bd412118231d3a89450a44f595af8 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Wed, 24 Dec 2014 15:36:54 +0100 Subject: [PATCH 6/8] Update pam_limits.py - Comments are now managed correctly - Cannot use use_min and use_max at the same time --- system/pam_limits.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index fde70abd800..2a6bec383fd 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2014, Sebastien Rohaut @@ -110,7 +109,8 @@ def main(): use_max = dict(default=False, type='bool'), use_min = dict(default=False, type='bool'), backup = dict(default=False, type='bool'), - dest = dict(default=limits_conf, type='str') + dest = dict(default=limits_conf, type='str'), + comment = dict(required=False, default='', type='str') ) ) @@ -122,6 +122,7 @@ def main(): use_min = module.params['use_min'] backup = module.params['backup'] limits_conf = module.params['dest'] + new_comment = module.params['comment'] changed = False @@ -131,6 +132,9 @@ def main(): else: module.fail_json(msg="%s is not visible (check presence, access rights, use sudo)" % (limits_conf) ) + if use_max and use_min: + module.fail_json(msg="Cannot use use_min and use_max at the same time." ) + # Backup if backup: backup_file = module.backup_local(limits_conf) @@ -146,6 +150,7 @@ def main(): new_value = value for line in f: + if line.startswith('#'): nf.write(line) continue @@ -155,6 +160,21 @@ def main(): nf.write(line) continue + # Remove comment in line + newline = newline.split('#',1)[0] + try: + old_comment = line.split('#',1)[1] + except: + old_comment = '' + + newline = newline.rstrip() + + if not new_comment: + new_comment = old_comment + + if new_comment: + new_comment = "\t#"+new_comment + line_fields = newline.split(' ') if len(line_fields) != 4: @@ -183,7 +203,7 @@ def main(): # Change line only if value has changed if new_value != actual_value: changed = True - new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + "\n" + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + new_comment + "\n" message = new_limit nf.write(new_limit) else: @@ -194,7 +214,7 @@ def main(): if not found: changed = True - new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + "\n" + new_limit = domain + "\t" + limit_type + "\t" + limit_item + "\t" + str(new_value) + new_comment + "\n" message = new_limit nf.write(new_limit) From 9cb97b28986548e20eb2e1e16c1ed810359b6b66 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Fri, 23 Jan 2015 20:07:27 +0100 Subject: [PATCH 7/8] Add "-" to ulimit type Just edited pam_types to add the '-', as explained in man 5 limits.conf --- system/pam_limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index 2a6bec383fd..649b9a175f7 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -95,7 +95,7 @@ def main(): pam_items = [ 'core', 'data', 'fsize', 'memlock', 'nofile', 'rss', 'stack', 'cpu', 'nproc', 'as', 'maxlogins', 'maxsyslogins', 'priority', 'locks', 'sigpending', 'msgqueue', 'nice', 'rtprio', 'chroot' ] - pam_types = [ 'soft', 'hard' ] + pam_types = [ 'soft', 'hard', '-' ] limits_conf = '/etc/security/limits.conf' From 44423e4a650d5b5f65640ecc451ad6a709efcb13 Mon Sep 17 00:00:00 2001 From: Sebastien ROHAUT Date: Sun, 21 Jun 2015 18:52:59 +0200 Subject: [PATCH 8/8] Update pam_limits.py Add version 2.0 Remove default: from documentation for required values use atomic_move from ansible module API --- system/pam_limits.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/system/pam_limits.py b/system/pam_limits.py index 649b9a175f7..ab429bb8808 100644 --- a/system/pam_limits.py +++ b/system/pam_limits.py @@ -1,3 +1,4 @@ +#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2014, Sebastien Rohaut @@ -25,7 +26,7 @@ import re DOCUMENTATION = ''' --- module: pam_limits -version_added: "historical" +version_added: "2.0" short_description: Modify Linux PAM limits description: - The M(pam_limits) module modify PAM limits, default in /etc/security/limits.conf. @@ -35,24 +36,20 @@ options: description: - A username, @groupname, wildcard, uid/gid range. required: true - default: null limit_type: description: - Limit type : hard or soft. required: true choices: [ "hard", "soft" ] - default: null limit_item: description: - The limit to be set : core, data, nofile, cpu, etc. required: true choices: [ "core", "data", "fsize", "memlock", "nofile", "rss", "stack", "cpu", "nproc", "as", "maxlogins", "maxsyslogins", "priority", "locks", "sigpending", "msgqueue", "nice", "rtprio", "chroot" ] - default: null value: description: - The value of the limit. required: true - default: null backup: description: - Create a backup file including the timestamp information so you can get @@ -222,10 +219,7 @@ def main(): nf.close() # Copy tempfile to newfile - shutil.copy(nf.name, f.name) - - # delete tempfile - os.unlink(nf.name) + module.atomic_move(nf.name, f.name) res_args = dict( changed = changed, msg = message