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.

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 Jan 29, 2020
1 parent 86a1cec commit e6d16d3
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 2 deletions.
64 changes: 64 additions & 0 deletions roles/StackStorm.mongodb/README.md
Original file line number Diff line number Diff line change
@@ -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

24 changes: 24 additions & 0 deletions roles/StackStorm.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/StackStorm.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_tasks: mongodb_auth.yml
tags: [databases, mongodb]
109 changes: 107 additions & 2 deletions roles/StackStorm.mongodb/tasks/mongodb_auth.yml
Original file line number Diff line number Diff line change
@@ -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]

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

0 comments on commit e6d16d3

Please sign in to comment.