Skip to content

Commit

Permalink
Add ability to enable mongo auth and add users
Browse files Browse the repository at this point in the history
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 StackStorm#75.
  • Loading branch information
cognifloyd committed Sep 22, 2018
1 parent 17b0256 commit 358475b
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
63 changes: 63 additions & 0 deletions roles/mongodb/README.md
Original file line number Diff line number Diff line change
@@ -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

24 changes: 24 additions & 0 deletions roles/mongodb/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions roles/mongodb/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
136 changes: 136 additions & 0 deletions roles/mongodb/tasks/mongodb_auth.yml
Original file line number Diff line number Diff line change
@@ -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 }}"

5 changes: 5 additions & 0 deletions roles/mongodb/vars/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') ) }}"

0 comments on commit 358475b

Please sign in to comment.