From e6d16d347ce04fb052e4cdabea0ab766a6a635ce 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. 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/StackStorm.mongodb/README.md | 64 ++++++++++ roles/StackStorm.mongodb/defaults/main.yml | 24 ++++ roles/StackStorm.mongodb/tasks/main.yml | 5 + .../StackStorm.mongodb/tasks/mongodb_auth.yml | 109 +++++++++++++++++- .../tasks/mongodb_auth_user.yml | 26 +++++ roles/StackStorm.mongodb/vars/main.yml | 2 + 6 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 roles/StackStorm.mongodb/README.md create mode 100644 roles/StackStorm.mongodb/tasks/mongodb_auth_user.yml diff --git a/roles/StackStorm.mongodb/README.md b/roles/StackStorm.mongodb/README.md new file mode 100644 index 00000000..88093dc9 --- /dev/null +++ b/roles/StackStorm.mongodb/README.md @@ -0,0 +1,64 @@ +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). +Enabling mongo auth also requires PyYAML to validate changes to mongod.conf. + +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/StackStorm.mongodb/defaults/main.yml b/roles/StackStorm.mongodb/defaults/main.yml index d59081d8..46cfe493 100644 --- a/roles/StackStorm.mongodb/defaults/main.yml +++ b/roles/StackStorm.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/StackStorm.mongodb/tasks/main.yml b/roles/StackStorm.mongodb/tasks/main.yml index b402607a..fed8ec0e 100644 --- a/roles/StackStorm.mongodb/tasks/main.yml +++ b/roles/StackStorm.mongodb/tasks/main.yml @@ -10,3 +10,8 @@ state: started enabled: yes tags: [databases, mongodb] + +- name: Enable mongodb authentication + when: mongodb_enable_auth + include_tasks: mongodb_auth.yml + tags: [databases, mongodb] diff --git a/roles/StackStorm.mongodb/tasks/mongodb_auth.yml b/roles/StackStorm.mongodb/tasks/mongodb_auth.yml index 8f4f2de6..1c9ae084 100644 --- a/roles/StackStorm.mongodb/tasks/mongodb_auth.yml +++ b/roles/StackStorm.mongodb/tasks/mongodb_auth.yml @@ -1,8 +1,10 @@ --- -- name: Install pip (for the installation of pymongo) +- name: Install role dependencies for mongodb config and validation become: yes package: - name: python-pip + name: + - python-pip # for the installation of pymongo + - "{% if ansible_facts.os_family == 'RedHat' %}PyYAML{% else %}python-yaml{% endif %}" state: present tags: [databases, mongodb] @@ -13,3 +15,106 @@ pip: name: "pymongo>=3.10.1,<4.0.0" tags: [databases, mongodb] + +- name: See if mongodb authorization is enabled and users are configured + command: 'mongo --eval "db.getUsers()" admin' + # 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 + tags: [databases, mongodb] + +- name: Show mongodb auth check output + # the purpose of this task is to make sure task output is visible in travis if there are problems + debug: + var: _mongo_authorization + verbosity: 2 + tags: [databases, mongodb] + +- 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!" + tags: [databases, mongodb] + +- 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: admin + roles: userAdminAnyDatabase + + # This does NOT include the mongo_auth_user.yml because of the + # special omit handling of login_* when mongo auth is not enabled/configured + login_host: "{{ mongodb_host }}" + login_port: "{{ mongodb_port | string }}" # silence implicit int->str conversion warning + 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, 'admin') }}" + tags: [databases, mongodb] + +- name: Enable security section in mongod.conf + become: yes + lineinfile: + path: /etc/mongod.conf + regexp: |- + ^[#'"\s]*security['"]?\s*: + line: 'security:' + validate: | + {{ mongodb_python }} -c ' + import yaml, io + if "security" not in yaml.safe_load(io.open("%s")): + exit(1) + ' + tags: [databases, mongodb] + +- 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: | + {{ mongodb_python }} -c ' + import yaml, io + if yaml.safe_load(io.open("%s"))["security"]["authorization"] != "enabled": + exit(1) + ' + register: _enable_mongo_auth + #notify: restart mongodb + tags: [databases, mongodb] + +- name: Restart mongodb to enable auth before adding additional users + # This is not a handler because restarting mongo now simplifies adding users. + # Restarting allows us to safely assume auth is already enabled. + when: + - mongodb_users | bool + - _enable_mongo_auth is changed + become: yes + service: + name: mongod + state: restarted + tags: [databases, mongodb] + +- name: Add additional mongo users + include_tasks: mongodb_auth_user.yml + loop_control: + loop_var: _mongodb_user + loop: "{{ mongodb_users }}" + # using loop_control: label does not obscure the password in output for verbosity > 1 + # So, loop over an include where the task name will include the username + db, but the loop var won't print out. + no_log: yes + tags: [databases, mongodb] diff --git a/roles/StackStorm.mongodb/tasks/mongodb_auth_user.yml b/roles/StackStorm.mongodb/tasks/mongodb_auth_user.yml new file mode 100644 index 00000000..103ec345 --- /dev/null +++ b/roles/StackStorm.mongodb/tasks/mongodb_auth_user.yml @@ -0,0 +1,26 @@ +--- +- name: "Add mongo auth user - {{ _mongodb_user.username }} on {{ _mongodb_user.db }}" + 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: "{{ _mongodb_user.username }}" + password: "{{ _mongodb_user.password }}" + database: "{{ _mongodb_user.db }}" + roles: "{{ _mongodb_user.roles|default('readWrite') }}" + + login_host: "{{ mongodb_host }}" + login_port: "{{ mongodb_port }}" + login_user: "{{ mongodb_admin_username }}" + login_password: "{{ mongodb_admin_password }}" + login_database: admin + tags: [databases, mongodb] diff --git a/roles/StackStorm.mongodb/vars/main.yml b/roles/StackStorm.mongodb/vars/main.yml index 6f0c4579..43198725 100644 --- a/roles/StackStorm.mongodb/vars/main.yml +++ b/roles/StackStorm.mongodb/vars/main.yml @@ -4,3 +4,5 @@ mongodb_major_minor_version: "{{ (mongodb_version|string)[:3] }}" mongodb_apt_keys: "3.2": "42F3E95A2C4F08279C4960ADD68FA50FEA312927" "3.4": "0C49F3730359A14518585931BC711F9BA15703C6" + +mongodb_python: "{{ ansible_python_interpreter | default( (ansible_python|default({})).get('executable', 'python') ) }}"