diff --git a/web_infrastructure/htpasswd b/web_infrastructure/htpasswd new file mode 100644 index 00000000000..94dde60c077 --- /dev/null +++ b/web_infrastructure/htpasswd @@ -0,0 +1,162 @@ +#!/usr/bin/env python +DOCUMENTATION = """ +module: htpasswd +short_description: manage user files for basic authentication +description: + - Add and remove username/password entries in a password file using htpasswd. + - This is used by web servers such as Apache and Nginx for basic authentication. +options: + path: + required: true + aliases: [ dest, destfile ] + description: + - Path to the file that contains the usernames and passwords + name: + required: true + aliases: [ username ] + description: + - User name to add or remove + password: + required: false + description: + - Password associated with user. + - Must be specified if user does not exist yet + state: + required: false + choices: [ present, absent ] + default: "present" + description: + - Whether the user entry should be present or not + create: + required: false + choices: [ "yes", "no" ] + default: "yes" + description: + - Used with C(state=present). If specified, the file will be created + if it does not already exist. If set to "no", will fail if the + file does not exist +requires: [ passlib>=1.6 ] +author: Lorin Hochstein +""" + +EXAMPLES = """ +# Add a user to a password file and ensure permissions are set +- htpasswd: path=/etc/nginx/passwdfile name=janedoe password=9s36?;fyNp owner=root group=www-data mode=0640 +# Remove a user from a password file +- htpasswd: path=/etc/apache2/passwdfile name=foobar state=absent +""" + + +import os + +try: + from passlib.apache import HtpasswdFile +except ImportError: + passlib_installed = False +else: + passlib_installed = True + + +def create_missing_directories(dest): + destpath = os.path.dirname(dest) + if not os.path.exists(destpath): + os.makedirs(destpath) + + +def present(dest, username, password, create, check_mode): + """ Ensures user is present + + Returns (msg, changed) """ + if not os.path.exists(dest): + if not create: + raise ValueError('Destination %s does not exist' % dest) + if check_mode: + return ("Create %s" % dest, True) + create_missing_directories(dest) + ht = HtpasswdFile(dest, new=True) + ht.set_password(username, password) + ht.save() + return ("Created %s and added %s" % (dest, username), True) + else: + ht = HtpasswdFile(dest, new=False) + if ht.check_password(username, password): + return ("%s already present" % username, False) + else: + if not check_mode: + ht.set_password(username, password) + ht.save() + return ("Add/update %s" % username, True) + + +def absent(dest, username, check_mode): + """ Ensures user is absent + + Returns (msg, changed) """ + if not os.path.exists(dest): + raise ValueError("%s does not exists" % dest) + + ht = HtpasswdFile(dest, new=False) + if username not in ht.users(): + return ("%s not present" % username, False) + else: + if not check_mode: + ht.delete(username) + ht.save() + return ("Remove %s" % username, True) + + +def check_file_attrs(module, changed, message): + + file_args = module.load_file_common_arguments(module.params) + if module.set_file_attributes_if_different(file_args, False): + + if changed: + message += " and " + changed = True + message += "ownership, perms or SE linux context changed" + + return message, changed + + +def main(): + arg_spec = dict( + path=dict(required=True, aliases=["dest", "destfile"]), + name=dict(required=True, aliases=["username"]), + password=dict(required=False, default=None), + state=dict(required=False, default="present"), + create=dict(type='bool', choices=BOOLEANS, default='yes'), + + ) + module = AnsibleModule(argument_spec=arg_spec, + add_file_common_args=True, + supports_check_mode=True) + + path = module.params['path'] + username = module.params['name'] + password = module.params['password'] + state = module.params['state'] + create = module.params['create'] + check_mode = module.check_mode + + if not passlib_installed: + module.fail_json(msg="This module requires the passlib Python library") + + try: + if state == 'present': + (msg, changed) = present(path, username, password, create, check_mode) + elif state == 'absent': + (msg, changed) = absent(path, username, check_mode) + else: + module.fail_json(msg="Invalid state: %s" % state) + + check_file_attrs(module, changed, msg) + module.exit_json(msg=msg, changed=changed) + except Exception, e: + module.fail_json(msg=str(e)) + + +# this is magic, see lib/ansible/module_common.py +#<> + +if __name__ == '__main__': + main()