From 358475b666d8fa6412481ddec8fff6e6dd38965f Mon Sep 17 00:00:00 2001 From: Jacob Floyd Date: Tue, 5 Sep 2017 13:57:33 -0500 Subject: [PATCH] Add ability to enable mongo auth and add users Adds auth to mongo when the mongodb_auth_enable flag is true. Any additional users should be passed in via mongodb_users. There are several other default variables as well (like mongodb_host, mongodb_port) that can be overridden in the play or inventory that uses this role. This does not attempt to generate any passwords. It only adds users if explicitly requested, but allows external tasks/roles to import the mongodb_auth.yml tasks to add users as required after mongo is installed. This uses lineinfile to edit mongod.conf with regexs to catch as many edge cases in yaml formatting (spaces, quotes) as possible. Also, this uses a bit of python to validate that the yaml file was modified in such a way that it is still valid yaml, and the entries intrduced in the file are present as expected. This uses the mongo shell to see if authorization is required before adding the admin user. This should handle cases such as the localhost exception in a new install or upgrades where auth is not enabled or upgrades where auth is enabled. This installs pip using system packages if `pip` is not present. `pip` is needed to install pymongo which is used to manage mongo users through the mongodb_user ansible module. As explained in comments, we only update mongo user passwords on_create because that is the only way to maintain idempotency. This should be idempotent. Part of #75. --- roles/mongodb/README.md | 63 +++++++++++++ roles/mongodb/defaults/main.yml | 24 +++++ roles/mongodb/tasks/main.yml | 5 + roles/mongodb/tasks/mongodb_auth.yml | 136 +++++++++++++++++++++++++++ roles/mongodb/vars/main.yml | 5 + 5 files changed, 233 insertions(+) create mode 100644 roles/mongodb/README.md create mode 100644 roles/mongodb/tasks/mongodb_auth.yml diff --git a/roles/mongodb/README.md b/roles/mongodb/README.md new file mode 100644 index 00000000..81f5f290 --- /dev/null +++ b/roles/mongodb/README.md @@ -0,0 +1,63 @@ +Ansible Role: stackstorm.mongodb +================================ + +Installs MongoDB. If the `mongodb_auth_enable` boolean is enabled, then this also enables authentication, adds an admin +user, and adds any other users defined in `mongodb_users`. + +Requirements +------------ + +Enabling mongo auth requires the pymongo python module (requirement of the mongo_users ansible module). + +Role Variables +-------------- + +These default variables can be set in the inventory's group or host vars, or pass them in as vars in the playbook that +uses this role. An example of passing in some of these vars is shown in an example playbook below. + +* `mongodb_version`: The major.minor version to install (only 3.4 or 3.2 are supported). +* `mongodb_enable_auth`: Whether or not to enable auth in mongodb (default: no) +* `mongodb_host`: Login to this host to add users (default '127.0.0.1') +* `mongodb_port`: Login on this port to add users (default '27017') +* `mongodb_admin_username`: The admin's username (default 'admin') +* `mongodb_admin_password`: The admin's password (default: generate a random password and store it in a file) +* `mongodb_users`: A list of users to add (see example playbook below; default: []) +* `mongodb_creds_dir`: The directory that should hold any generated credentials like admin (default: '.') + +Dependencies +------------ + +On RedHat family distributions, this depends on the stackstorm.epel role. + +Example Playbook +---------------- + +This playbook installs mongo without enabling auth or adding any users: + + - hosts: localhost + roles: + - role: StackStorm.stackstorm/roles/mongodb + + +This playbook installs mongo, enables auth, and adds a stackstorm user, and force update the password if it already exists: + + - hosts: localhost + roles: + - role: StackStorm.stackstorm/roles/mongodb + vars: + mongodb_enable_auth: yes + mongodb_users: + - db: st2 + username: st2mongo + password: "{{ lookup('password', '{{ mongodb_creds_dir }}/mongodb-' + inventory_hostname + '-' + st2mongo_username + ' length=42' ) }}" + roles: readWrite + mongodb_force_password_update: yes + +Note that the `readWrite` mongo role is used by default, so `roles` can be ommitted for the above playbook. +You can use the roles attribute to add any other mongo roles to your user. + +License +------- + +Apache 2.0 + diff --git a/roles/mongodb/defaults/main.yml b/roles/mongodb/defaults/main.yml index d59081d8..46cfe493 100644 --- a/roles/mongodb/defaults/main.yml +++ b/roles/mongodb/defaults/main.yml @@ -2,3 +2,27 @@ # MongoDB version to install # Should be '3.2' or '3.4' mongodb_version: "3.4" + +# do not enable mongo auth by default. Make sure to add required users in mongodb_users if auth is enabled. +mongodb_enable_auth: no + +# when adding auth, the login credentials to use +mongodb_host: 127.0.0.1 +mongodb_port: 27017 +mongodb_admin_username: admin +# For production use - please change the admin password! +mongodb_admin_password: "{{ mongodb_default_admin_password }}" +# The default is separate so the st2 role can provide a default without overriding a user provided password. +mongodb_default_admin_password: "Ch@ngeMe" + +# Additional users to add. +mongodb_users: [] +# - db: st2 +# username: st2mongo +# password: "Ch@ngeMe" +# roles: readWrite + +# whether or not to force a password update for any users in mongodb_users +# Setting this to yes will result in 'changed' on every run, even if the password is the same. +# See the comment in tasks/mongodb_auth.yml for more details. +mongodb_force_update_password: no diff --git a/roles/mongodb/tasks/main.yml b/roles/mongodb/tasks/main.yml index d0fc890e..127405da 100644 --- a/roles/mongodb/tasks/main.yml +++ b/roles/mongodb/tasks/main.yml @@ -10,3 +10,8 @@ state: started enabled: yes tags: [databases, mongodb] + +- name: Enable mongodb authentication + when: mongodb_enable_auth + include: mongodb_auth.yml + tags: [databases, mongodb] diff --git a/roles/mongodb/tasks/mongodb_auth.yml b/roles/mongodb/tasks/mongodb_auth.yml new file mode 100644 index 00000000..4e611908 --- /dev/null +++ b/roles/mongodb/tasks/mongodb_auth.yml @@ -0,0 +1,136 @@ +--- +- name: Check for pip (used to install pymongo) + command: "{{ pip | default('pip') }} --version" + register: pip_check + changed_when: no + failed_when: no + +# It shouldn't matter if it's an old version of pip for installing pymongo, so use the system package instead of get-pip.py +- name: Install pip with system package (used to install pymongo) + when: pip_check.rc != 0 + become: yes + package: + name: 'python{% ansible_os_family == "RedHat" and ansible_distribution_major_version == "7" %}2{% endif %}-pip' + state: present + +- name: Ensure pymongo is installed for the mongodb_user ansible module + become: yes + pip: + name: pymongo + +- name: See if mongodb authorization is enabled and users are configured + command: 'mongo --eval "db.getUsers()" {{ mongodb_admin_db }}' + # this will fail (rc 252) if auth is enabled and users are configured + # With the localhost exception, this will succeed (rc 0) when users are not configured even if auth is enabled. + # see: + # - https://docs.mongodb.com/manual/core/security-users/#localhost-exception + # - https://stackoverflow.com/q/31949586/1134951 + # - https://github.com/ansible/ansible/issues/33832#issuecomment-358031733 + register: _mongo_authorization + changed_when: no # this does not change anything, it only checks + failed_when: no # The rc determines whether or not auth is required to create/update the admin user + +- name: Show mongodb auth check output + debug: + var: _mongo_authorization + verbosity: 2 + +- name: Warn about default credentials + when: "mongodb_admin_password == 'Ch@ngeMe'" + debug: + msg: "[WARNING] Using default admin credentials for mongodb admin account! Please change them!" + +- name: Add mongo admin + mongodb_user: + state: present + + # NOTE: on_create is idempotent - see comment below + update_password: on_create + + name: "{{ mongodb_admin_username }}" + password: "{{ mongodb_admin_password }}" + database: "{{ mongodb_admin_db }}" + roles: userAdminAnyDatabase + + login_host: "{{ mongodb_host }}" + login_port: "{{ mongodb_port }}" + login_user: "{{ (_mongo_authorization.rc == 0) | ternary(omit, mongodb_admin_username) }}" + login_password: "{{ (_mongo_authorization.rc == 0) | ternary(omit, mongodb_admin_password) }}" + login_database: "{{ (_mongo_authorization.rc == 0) | ternary(omit, mongodb_admin_db) }}" + +- name: Enable security section in mongod.conf + become: yes + lineinfile: + path: /etc/mongod.conf + regexp: |- + ^[#'"\s]*security['"]?\s*: + line: 'security:' + validate: | + {{ ansible_python_interpreter|default("/usr/bin/python") }} -c ' + import yaml, io + if "security" not in yaml.safe_load(io.open("%s")): + exit(1) + ' + +- name: Enable authentication in mongod.conf + become: yes + lineinfile: + path: /etc/mongod.conf + insertafter: '^security:' + # two space indentation (the default) assumed + line: ' authorization: enabled' + regexp: |- + ^[#'"\s]+authorization['"]?\s*: + validate: | + {{ ansible_python_interpreter|default("/usr/bin/python") }} -c ' + import yaml, io + if yaml.safe_load(io.open("%s"))["security"]["authorization"] != "enabled": + exit(1) + ' + register: _enable_mongo_auth + #notify: restart mongodb + +- name: Restart mongodb to enable auth before adding additional users + # This allows us to safely assume auth is already enabled when adding more users + when: + - mongodb_users + - _enable_mongo_auth is changed + become: yes + service: + name: mongod + state: restarted +# TODO: Use filtered flush_handlers once this is merged: https://github.com/ansible/ansible/pull/25573 +# As is, mongo may restart twice, once here and whenever the upgrade handlers get flushed. +# when: mongodb_users +# meta: +# name: flush_handlers +# filter: restart mongodb + +- name: Add additional mongo users + mongodb_user: + state: present + + # NOTE: on_create is idempotent, always is not. + # With `update_password: on_create`, mongodb_user checks to see if the user + # (a) exists on the db, and (b) has the same roles, + # and then it only adds the user if it's not there or the roles have changed. + # With `update_password: always`, mongodb_user cannot tell if the password + # needs to be changed without attempting a login with those credentials. + # But mongodb_user does not currently implement such a check. + # A comment in mongodb_user points to https://jira.mongodb.org/browse/SERVER-22848 + update_password: "{{ mongodb_force_update_password|ternary('always', 'on_create') }}" + + name: "{{ item.username }}" + password: "{{ item.password }}" + database: "{{ item.db }}" + roles: "{{ item.roles|default('readWrite') }}" + + login_host: "{{ mongodb_host }}" + login_port: "{{ mongodb_port }}" + login_user: "{{ mongodb_admin_username }}" + login_password: "{{ mongodb_admin_password }}" + login_database: "{{ mongodb_admin_db }}" + loop_control: + label: "{{ item.username }} on {{ item.db }}" + with_items: "{{ mongodb_users }}" + diff --git a/roles/mongodb/vars/main.yml b/roles/mongodb/vars/main.yml index 6f0c4579..11c1ecfb 100644 --- a/roles/mongodb/vars/main.yml +++ b/roles/mongodb/vars/main.yml @@ -4,3 +4,8 @@ mongodb_major_minor_version: "{{ (mongodb_version|string)[:3] }}" mongodb_apt_keys: "3.2": "42F3E95A2C4F08279C4960ADD68FA50FEA312927" "3.4": "0C49F3730359A14518585931BC711F9BA15703C6" + +# The admin database, used to add users +mongodb_admin_db: admin + +mongodb_python: "{{ ansible_python_interpreter | default( ansible_python.executable | default('python') ) }}"