Skip to content

Commit 4538088

Browse files
Johan De Witstevenpost
Johan De Wit
authored andcommitted
Work around authentication requirement before user creation
On the initial setup, a user doesn't yet exists so we can't authenticate. However we require authentication. We work around this by connecting unauthenticated using the loopback address.
1 parent 9106683 commit 4538088

File tree

3 files changed

+104
-47
lines changed

3 files changed

+104
-47
lines changed

lib/puppet/provider/mongodb.rb

+24-7
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,13 @@ def self.db_ismaster
139139
cmd_ismaster = 'db.isMaster().ismaster'
140140
cmd_ismaster = mongoshrc_file + cmd_ismaster if mongoshrc_file
141141
db = 'admin'
142-
res = mongosh_cmd(db, conn_string, cmd_ismaster).to_s.split(%r{\n}).last.chomp
142+
143+
begin
144+
res = mongosh_cmd(db, conn_string, cmd_ismaster).to_s.split(%r{\n}).last.chomp
145+
rescue StandardError
146+
res = mongosh_cmd(db, conn_string, 'db.isMaster().ismaster').to_s.chomp if mongoshrc_file && res =~ %r{Authentication failed}
147+
end
148+
143149
res.eql?('true')
144150
end
145151

@@ -156,6 +162,7 @@ def self.auth_enabled(config = nil)
156162
def self.mongo_eval(cmd, db = 'admin', retries = 10, host = nil)
157163
retry_count = retries
158164
retry_sleep = 3
165+
no_auth_cmd = cmd
159166
cmd = mongoshrc_file + cmd if mongoshrc_file
160167

161168
out = nil
@@ -166,15 +173,25 @@ def self.mongo_eval(cmd, db = 'admin', retries = 10, host = nil)
166173
mongosh_cmd(db, conn_string, cmd)
167174
end
168175
rescue StandardError => e
169-
retry_count -= 1
170-
if retry_count.positive?
171-
Puppet.debug "Request failed: '#{e.message}' Retry: '#{retries - retry_count}'"
172-
sleep retry_sleep
173-
retry
176+
# When using the rc file, we get this eror because in most cases the admin user is not yet created
177+
# Can/must we move this out of the rescue block ?
178+
if auth_enabled && e.message =~ %r{Authentication failed}
179+
out = if host
180+
mongosh_cmd(db, host, no_auth_cmd)
181+
else
182+
mongosh_cmd(db, conn_string, no_auth_cmd)
183+
end
184+
else
185+
retry_count -= 1
186+
if retry_count.positive?
187+
sleep retry_sleep
188+
retry
189+
end
174190
end
175191
end
176192

177-
raise Puppet::ExecutionFailure, "Could not evaluate MongoDB shell command: #{cmd}" unless out
193+
# return also the error message, so caller can react on it
194+
raise Puppet::ExecutionFailure, "Could not evaluate MongoDB shell command: #{cmd} with #{e.message}" unless out
178195

179196
Puppet::Util::MongodbOutput.sanitize(out)
180197
end

lib/puppet/provider/mongodb_database/mongodb.rb

+18-4
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,26 @@ def self.prefetch(resources)
2727
end
2828
end
2929

30+
def auth_enabled
31+
self.class.auth_enabled
32+
end
33+
3034
def create
31-
if db_ismaster
32-
out = mongo_eval('db.dummyData.insert({"created_by_puppet": 1})', @resource[:name])
33-
raise "Failed to create DB '#{@resource[:name]}'\n#{out}" if %r{writeError} =~ out
34-
else
35+
unless db_ismaster
3536
Puppet.warning 'Database creation is available only from master host'
37+
return
38+
end
39+
40+
begin
41+
out = mongo_eval('db.dummyData.insertOne({"created_by_puppet": 1})', @resource[:name])
42+
rescue StandardError => e
43+
if auth_enabled && e.message =~ %r{not authorized on admin to execute commanda} && @resource[:name] == 'admin'
44+
Puppet.warning 'Skipping database creation for admin, need admin user first when security is enabled'
45+
@property_hash[:ensure] = :present
46+
@property_hash[:name] = @resource[:name]
47+
elsif out =~ %r{writeError}
48+
raise "Failed to create DB '#{@resource[:name]}'\n#{out}"
49+
end
3650
end
3751
end
3852

lib/puppet/provider/mongodb_replset/mongo.rb

+62-36
Original file line numberDiff line numberDiff line change
@@ -133,51 +133,70 @@ def self.replset_properties
133133
conn_string = conn_string
134134
begin
135135
output = mongo_command('rs.conf()', conn_string)
136-
rescue Puppet::ExecutionFailure
137-
output = {}
138-
end
139-
if output['members']
140-
return {
141-
name: output['_id'], # replica set name
142-
ensure: :present,
143-
members: output['members'],
144-
settings: output['settings'],
145-
provider: :mongo
146-
}
136+
if output['members']
137+
return {
138+
name: output['_id'], # replica set name
139+
ensure: :present,
140+
members: output['members'],
141+
settings: output['settings'],
142+
provider: :mongo
143+
}
144+
end
145+
nil
146+
rescue Puppet::ExecutionFailure => e
147+
if e.message =~ %r{command replSetGetConfig requires authentication} || e.message =~ %r{not authorized on admin to execute command}
148+
output = mongo_command('rs.status()', host)
149+
if output['members']
150+
memb = []
151+
output['members'].each do |m|
152+
memb << { 'host' => m['name'] }
153+
end
154+
return {
155+
name: output['set'],
156+
ensure: :present,
157+
members: memb,
158+
provider: :mongo
159+
}
160+
end
161+
nil
162+
end
147163
end
148-
nil
149164
end
150165

151166
def get_hosts_status(members)
152167
alive = []
153168
members.select do |member|
154169
host = member['host']
155170
Puppet.debug "Checking replicaset member #{host} ..."
156-
status = rs_status(host)
157-
raise Puppet::Error, "Can't configure replicaset #{name}, host #{host} is not supposed to be part of a replicaset." if status.key?('errmsg') && status['errmsg'] == 'not running with --replSet'
158-
159-
if auth_enabled && status.key?('errmsg') && (status['errmsg'].include?('unauthorized') || status['errmsg'].include?('not authorized') || status['errmsg'].include?('requires authentication'))
160-
Puppet.warning "Host #{host} is available, but you are unauthorized because of authentication is enabled: #{auth_enabled}"
161-
alive.push(member)
162-
end
163-
164-
if status.key?('errmsg') && status['errmsg'].include?('no replset config has been received')
165-
Puppet.debug 'Mongo v4 rs.status() RS not initialized output'
166-
alive.push(member)
167-
end
168-
169-
if status.key?('set')
170-
raise Puppet::Error, "Can't configure replicaset #{name}, host #{host} is already part of another replicaset." if status['set'] != name
171-
172-
# This node is alive and supposed to be a member of our set
173-
Puppet.debug "Host #{host} is available for replset #{status['set']}"
174-
alive.push(member)
175-
elsif status.key?('info')
176-
Puppet.debug "Host #{host} is alive but unconfigured: #{status['info']}"
177-
alive.push(member)
171+
begin
172+
status = rs_status(host)
173+
raise Puppet::Error, "Can't configure replicaset #{name}, host #{host} is not supposed to be part of a replicaset." if status.key?('errmsg') && status['errmsg'] == 'not running with --replSet'
174+
175+
if status.key?('set')
176+
raise Puppet::Error, "Can't configure replicaset #{name}, host #{host} is already part of another replicaset." if status['set'] != name
177+
178+
# This node is alive and supposed to be a member of our set
179+
Puppet.debug "Host #{host} is available for replset #{status['set']}"
180+
alive.push(member)
181+
elsif status.key?('info')
182+
Puppet.debug "Host #{host} is alive but unconfigured: #{status['info']}"
183+
alive.push(member)
184+
end
185+
rescue Puppet::ExecutionFailure => e
186+
if auth_enabled
187+
case e.message
188+
when %r{no replset config has been received}
189+
Puppet.warning('No replicaset config received, needs initialisation')
190+
when %r{Authentication failed}, %r{not authorized on admin}
191+
Puppet.warning "Host #{host} is available, but you are unauthorized because of authentication is enabled: #{auth_enabled}"
192+
when %r{command replSetGetStatus requires authentication}
193+
Puppet.warning("Node #{host} is reachable but requires authentication: ReplicaSet not initialized")
194+
end
195+
alive.push(member)
196+
else
197+
Puppet.warning "Can't connect to replicaset member #{host} (Errormsg: #{e.message})."
198+
end
178199
end
179-
rescue Puppet::ExecutionFailure
180-
Puppet.warning "Can't connect to replicaset member #{host}."
181200
end
182201
alive.uniq!
183202
dead = members - alive
@@ -233,6 +252,13 @@ def set_members
233252
end
234253

235254
Puppet.debug 'Checking for dead and alive members'
255+
# When no replicaset is initiated yet, and authenticatoin is anabled,
256+
# mongo_eval still adds the mongorcsh.js. This gives an 'MongoServerError: Authentication failed.' error.
257+
# In this stage, we only can connect to localhost, and only rs.status() and rs.initiate() is possible.
258+
# All other commands generate 'MongoServerError: not authorized on admin to execute command' error
259+
# So we need to check first if the replicaset is already available, then the admin user can be created, and after that
260+
# authentication should be working.
261+
#
236262
if !@property_flush[:members].nil? && !@property_flush[:members].empty?
237263
# Find the alive members so we don't try to add dead members to the replset using new config
238264
alive_hosts, dead_hosts = get_hosts_status(@property_flush[:members])

0 commit comments

Comments
 (0)