Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User management in replicasets / sharding #344

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a11f293
adding authentication setting for admin separate from user creation
cjhubert Sep 10, 2014
930303e
instructions on how to update admin password and making the defaults …
cjhubert Sep 10, 2014
a840e84
handling attempting to create users on secondary / uninitialized repl…
cjhubert Sep 11, 2014
5859f47
waiting for replica set initialization before creating users
cjhubert Sep 11, 2014
e96aa49
updating read me for instructions on key file in replica sets
cjhubert Sep 11, 2014
9c7fc0c
removing random semi-colons from line termination
cjhubert Sep 11, 2014
3c603f7
switching from immediately to delayed
cjhubert Sep 12, 2014
694edd4
adding ability to force creation of admin users in mongos since auth …
cjhubert Sep 16, 2014
bcd4950
Merge branch 'admin-user-update' of github.com:ceejh/chef-mongodb int…
cjhubert Sep 16, 2014
03e75d0
adding new attributes for handling mongos and mongod nodes
cjhubert Sep 16, 2014
bbde9b8
adding fallback strategy for mongod replica set nodes
cjhubert Sep 16, 2014
df7c00d
changing subscribed ruby_block
cjhubert Sep 16, 2014
54c8358
using a check if recipe is in run list instead of basing it off attri…
cjhubert Sep 17, 2014
fb38904
adding retry attempts to connection for when service is in the middle…
cjhubert Sep 17, 2014
fb0fe23
adding authentication to mongos commands where required in user_manag…
cjhubert Sep 17, 2014
c0577b7
giving a little more time to the connection retries as mongos can be …
cjhubert Sep 17, 2014
823d3dd
adding rescue connection failures to configuring shards since it can'…
cjhubert Sep 18, 2014
bcc62ad
rubocop warnings / typo
cjhubert Sep 18, 2014
cd06d19
adding instructions on mongos specific config
cjhubert Oct 9, 2014
d1e2ee2
fixing typo in node configuration value
cjhubert Oct 9, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,13 +246,24 @@ the `node['mongodb']['config']['auth']` attribute to true in the chef json.

If the auth configuration is true, it will try to create the `node['mongodb']['admin']` user, or
update them if they already exist. Before using on a new database, ensure you're overwriting
the `node['mongodb']['admin']['username']` and `node['mongodb']['admin']['password']` to
the `node['mongodb']['authentication']['username']` and `node['mongodb']['authentication']['password']` to
something besides their default values.

To update the admin username or password after already having deployed the recipe with authentication
as required, simply change `node['mongodb']['admin']['password']` to the new password while keeping the
value of `node['mongodb']['authentication']['password']` the old value. After the recipe runs successfully,
be sure to change the latter variable to the new password so that subsequent attempts to authenticate will
work.

There's also a user resource which has the actions `:add`, `:modify` and `:delete`. If modify is
used on a user that doesn't exist, it will be added. If add is used on a user that exists, it
will be modified.

If using this recipe with replication and sharding, ensure that the `node['mongodb']['key_file_content']`
is set. All nodes must have the same key file in order for the replica set to initialize successfully
when authentication is required. For mongos instances, set `node['mongodb']['mongos_create_admin']` to
`true` to force the creation of the admin user on mongos instances.

# LICENSE and AUTHOR:

Author:: Markus Korn <[email protected]>
Expand Down
27 changes: 24 additions & 3 deletions attributes/users.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
# The username / password combination that is used
# to authenticate with the mongo database
default['mongodb']['authentication']['username'] = 'admin'
default['mongodb']['authentication']['password'] = 'admin'

default['mongodb']['admin'] = {
'username' => 'admin',
'password' => 'admin',
'roles' => %w(userAdminAnyDatabase dbAdminAnyDatabase),
'username' => default['mongodb']['authentication']['username'],
'password' => default['mongodb']['authentication']['password'],
'roles' => %w(userAdminAnyDatabase dbAdminAnyDatabase clusterAdmin),
'database' => 'admin'
}

default['mongodb']['users'] = []

# Force creation of admin user. auth=true is an invalid
# setting for mongos so this is needed to ensure the admin
# user is created
default['mongodb']['mongos_create_admin'] = false

# For connecting to mongo on localhost, retries to make after
# connection failures and delay in seconds to retry
default['mongodb']['user_management']['connection']['retries'] = 2
default['mongodb']['user_management']['connection']['delay'] = 2

# For mongod replicasets, the delay in seconds and number
# of times to retry adding a user. Used to handle election
# of primary not being completed immediately
default['mongodb']['mongod_create_user']['retries'] = 2
default['mongodb']['mongod_create_user']['delay'] = 10
28 changes: 26 additions & 2 deletions libraries/mongodb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,26 @@ def self.configure_shards(node, shard_nodes)
Chef::Log.info(shard_members.inspect)

begin
connection = Mongo::Connection.new('localhost', node['mongodb']['config']['port'], :op_timeout => 5)
connection = nil
rescue_connection_failure do
connection = Mongo::Connection.new('localhost', node['mongodb']['config']['port'], :op_timeout => 5)
end
rescue => e
Chef::Log.warn("Could not connect to database: 'localhost:#{node['mongodb']['config']['port']}', reason #{e}")
return
end

admin = connection['admin']

# If we require authentication on mongos / mongod, need to authenticate to run these commands
if node.recipe?('mongodb::user_management')
begin
admin.authenticate(node['mongodb']['authentication']['username'], node['mongodb']['authentication']['password'])
rescue Mongo::AuthenticationError => e
Chef::Log.warn("Unable to authenticate with database to add shards to mongos node: #{e}")
end
end

shard_members.each do |shard|
cmd = BSON::OrderedHash.new
cmd['addShard'] = shard
Expand All @@ -267,14 +279,26 @@ def self.configure_sharded_collections(node, sharded_collections)
require 'mongo'

begin
connection = Mongo::Connection.new('localhost', node['mongodb']['config']['port'], :op_timeout => 5)
connection = nil
rescue_connection_failure do
connection = Mongo::Connection.new('localhost', node['mongodb']['config']['port'], :op_timeout => 5)
end
rescue => e
Chef::Log.warn("Could not connect to database: 'localhost:#{node['mongodb']['config']['port']}', reason #{e}")
return
end

admin = connection['admin']

# If we require authentication on mongos / mongod, need to authenticate to run these commands
if node.recipe?('mongodb::user_management')
begin
admin.authenticate(node['mongodb']['authentication']['username'], node['mongodb']['authentication']['password'])
rescue Mongo::AuthenticationError => e
Chef::Log.warn("Unable to authenticate with database to configure databased on mongos node: #{e}")
end
end

databases = sharded_collections.keys.map { |x| x.split('.').first }.uniq
Chef::Log.info("enable sharding for these databases: '#{databases.inspect}'")

Expand Down
84 changes: 70 additions & 14 deletions providers/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,67 @@ def add_user(username, password, roles = [], database)
# Check if user is admin / admin, and warn that this should
# be overridden to unique values
if username == 'admin' && password == 'admin'
Chef::Log.warn('Default username / password detected for admin user');
Chef::Log.warn('These should be overridden to different, unique values');
Chef::Log.warn('Default username / password detected for admin user')
Chef::Log.warn('These should be overridden to different, unique values')
end

# If authentication is required on database
# must authenticate as a userAdmin after an admin user has been created
# this will fail on the first attempt, but user will still be created
# because of the localhost exception
if node['mongodb']['config']['auth'] == true
if (@new_resource.connection['config']['auth'] == true) || (@new_resource.connection['mongos_create_admin'] == true)
begin
admin.authenticate(@new_resource.connection['admin']['username'], @new_resource.connection['admin']['password'])
admin.authenticate(@new_resource.connection['authentication']['username'], @new_resource.connection['authentication']['password'])
rescue Mongo::AuthenticationError => e
Chef::Log.warn("Unable to authenticate as admin user. If this is a fresh install, ignore warning: #{e}")
end
end

# Create the user if they don't exist
# Update the user if they already exist
db.add_user(username, password, false, :roles => roles)
Chef::Log.info("Created or updated user #{username} on #{database}")
begin
db.add_user(username, password, false, :roles => roles)
Chef::Log.info("Created or updated user #{username} on #{database}")
rescue Mongo::ConnectionFailure => e
if @new_resource.connection['is_replicaset']
# Node is part of a replicaset and may not be initialized yet, going to retry if set to
i = 0
while i < @new_resource.connection['mongod_create_user']['retries']
begin
# See if we can get the current replicaset status back from the node
cmd = BSON::OrderedHash.new
cmd['replSetGetStatus'] = 1
result = admin.command(cmd)
# Check if the current node in the replicaset status has an info message set (at this point, most likely
# a message about the election)
has_info_message = result['members'].select { |a| a['self'] && a.key?('infoMessage') }.count > 0
if result['myState'] == 1
# This node is a primary node, try to add the user
db.add_user(username, password, false, :roles => roles)
Chef::Log.info("Created or updated user #{username} on #{database} of primary replicaset node")
break
elsif result['myState'] == 2 && has_info_message == true
# This node is secondary but may be in the process of an election, retry
Chef::Log.info("Unable to add user to secondary, election may be in progress, retrying in #{@new_resource.connection['mongod_create_user']['delay']} seconds...")
elsif result['myState'] == 2 && has_info_message == false
# This node is secondary and not in the process of an election, bail out
Chef::Log.info('Current node appears to be a secondary node in replicaset, could not detect election in progress, not adding user')
break
end
rescue Mongo::ConnectionFailure => e
# Unable to connect to the node, may not be initialized yet
Chef::Log.warn("Unable to add user, retrying in #{@new_resource.connection['mongod_create_user']['delay']} second(s)... #{e}")
rescue Mongo::OperationFailure => e
# Unable to make either add call or replicaset call on node, should retry in case it was in the middle of being initialized
Chef::Log.warn("Unable to add user, retrying in #{@new_resource.connection['mongod_create_user']['delay']} second(s)... #{e}")
end
i += 1
sleep(@new_resource.connection['mongod_create_user']['delay'])
end
else
Chef::Log.fatal("Unable to add user: #{e}")
end
end
end

# Drop a user from the database specified
Expand All @@ -44,7 +85,14 @@ def delete_user(username, database)
admin = connection.db('admin')
db = connection.db(database)

admin.authenticate(@new_resource.connection['admin']['username'], @new_resource.connection['admin']['password'])
# Only try to authenticate with db if required
if (@new_resource.connection['config']['auth'] == true) || (@new_resource.connection['mongos_create_admin'] == true)
begin
admin.authenticate(@new_resource.connection['authentication']['username'], @new_resource.connection['authentication']['password'])
rescue Mongo::AuthenticationError => e
Chef::Log.warn("Unable to authenticate as admin user: #{e}")
end
end

if user_exists?(username, connection)
db.remove_user(username)
Expand All @@ -55,16 +103,24 @@ def delete_user(username, database)
end

# Get the MongoClient connection
def retrieve_db
def retrieve_db(attempt = 0)
require 'rubygems'
require 'mongo'

Mongo::MongoClient.new(
@new_resource.connection['host'],
@new_resource.connection['port'],
:connect_timeout => 15,
:slave_ok => true
)
begin
Mongo::MongoClient.new(
@new_resource.connection['host'],
@new_resource.connection['port'],
:connect_timeout => 15,
:slave_ok => true
)
rescue Mongo::ConnectionFailure => e
if(attempt) < @new_resource.connection['user_management']['connection']['retries']
Chef::Log.warn("Unable to connect to MongoDB instance, retrying in #{@new_resource.connection['user_management']['connection']['delay']} second(s)...")
sleep(@new_resource.connection['user_management']['connection']['delay'])
retrieve_db(attempt + 1)
end
end
end

action :add do
Expand Down
9 changes: 7 additions & 2 deletions recipes/user_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

# If authentication is required,
# add the admin to the users array for adding/updating
users << admin if node['mongodb']['config']['auth'] == true
users << admin if (node['mongodb']['config']['auth'] == true) || (node['mongodb']['mongos_create_admin'] == true)

users.concat(node['mongodb']['users'])

Expand All @@ -16,6 +16,11 @@
roles user['roles']
database user['database']
connection node['mongodb']
action :add
if node.recipe?('mongodb::mongos') || node.recipe?('mongodb::replicaset')
# If it's a replicaset or mongos, don't make any users until the end
action :nothing
subscribes :add, 'ruby_block[config_replicaset]', :delayed
subscribes :add, 'ruby_block[config_sharding]', :delayed
end
end
end