Skip to content

Commit 65e2ad3

Browse files
author
Johan De Wit
committed
make ismaster work with authentication enbaled before user admin is created
1 parent 8be9ff3 commit 65e2ad3

File tree

7 files changed

+103
-32
lines changed

7 files changed

+103
-32
lines changed

lib/facter/is_master.rb

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,71 @@ def get_options_from_hash_config(config)
1616
# - sslMode is "requireSSL"
1717
# - Parameter --sslPEMKeyFile is set
1818
# - Parameter --sslCAFile is set
19-
result << "--ssl --host #{Facter.value(:fqdn)}" if ['allowSSL', 'preferSSL', 'requireSSL'].include? config['net.ssl.mode'] || !config['net.ssl.PEMKeyFile'].nil? || !config['net.ssl.CAFile'].nil?
19+
result << "--ssl --host #{Facter.value(:fqdn)}" if config['net.ssl.mode'] == 'requireSSL' || !config['net.ssl.PEMKeyFile'].nil? || !config['net.ssl.CAFile'].nil?
2020
result << "--sslPEMKeyFile #{config['net.ssl.PEMKeyFile']}" unless config['net.ssl.PEMKeyFile'].nil?
2121
result << "--sslCAFile #{config['net.ssl.CAFile']}" unless config['net.ssl.CAFile'].nil?
22-
result << "--sslAllowInvalidHostnames" if config['net.ssl.allowInvalidHostnames'] == true
2322
# use --tls and --host if:
2423
# - tlsMode is "requireTLS"
2524
# - Parameter --tlsCertificateKeyFile is set
2625
# - Parameter --tlsCAFile is set
27-
result << "--tls --host #{Facter.value(:fqdn)}" if ['allowTLS', 'prefeTLS', 'requireTLS'].include? config['net.tls.mode'] || !config['net.tls.certificateKeyFile'].nil? || !config['net.tls.CAFile'].nil?
26+
result << "--tls --host #{Facter.value(:fqdn)}" if config['net.tls.mode'] == 'requireTLS' || !config['net.tls.certificateKeyFile'].nil? || !config['net.tls.CAFile'].nil?
2827
result << "--tlsCertificateKeyFile #{config['net.tls.certificateKeyFile']}" unless config['net.tls.certificateKeyFile'].nil?
2928
result << "--tlsCAFile #{config['net.tls.CAFile']}" unless config['net.tls.CAFile'].nil?
30-
result << "--tlsAllowInvalidHostnames" if config['net.tls.allowInvalidHostnames'] == true
3129

3230
result << '--ipv6' unless config['net.ipv6'].nil?
3331

3432
result.join(' ')
3533
end
3634

35+
def get_options_from_keyvalue_config(file)
36+
config = {}
37+
File.readlines(file).map do |line|
38+
k, v = line.split('=')
39+
config[k.rstrip] = v.lstrip.chomp if k && v
40+
end
41+
42+
result = []
43+
44+
result << "--port #{config['port']}" unless config['port'].nil?
45+
# use --ssl and --host if:
46+
# - sslMode is "requireSSL"
47+
# - Parameter --sslPEMKeyFile is set
48+
# - Parameter --sslCAFile is set
49+
result << "--ssl --host #{Facter.value(:fqdn)}" if config['ssl'] == 'requireSSL' || !config['sslcert'].nil? || !config['sslca'].nil?
50+
result << "--sslPEMKeyFile #{config['sslcert']}" unless config['sslcert'].nil?
51+
result << "--sslCAFile #{config['sslca']}" unless config['sslca'].nil?
52+
# use --tls and --host if:
53+
# - tlsMode is "requireTLS"
54+
# - Parameter --tlsCertificateKeyFile is set
55+
# - Parameter --tlsCAFile is set
56+
result << "--tls --host #{Facter.value(:fqdn)}" if config['tls'] == 'requireTLS' || !config['tlscert'].nil? || !config['tlsca'].nil?
57+
result << "--tlsCertificateKeyFile #{config['tlscert']}" unless config['tlscert'].nil?
58+
result << "--tlsCAFile #{config['tlsca']}" unless config['tlsca'].nil?
59+
60+
result << '--ipv6' unless config['ipv6'].nil?
61+
62+
result.join(' ')
63+
end
64+
3765
def get_options_from_config(file)
3866
config = YAML.load_file(file)
39-
get_options_from_hash_config(config)
67+
if config.is_a?(Hash) # Using a valid YAML file for mongo 2.6
68+
get_options_from_hash_config(config)
69+
else # It has to be a key-value config file
70+
get_options_from_keyvalue_config(file)
71+
end
4072
end
4173

4274
Facter.add('mongodb_is_master') do
4375
setcode do
44-
if %w[mongo mongod].all? { |m| Facter::Util::Resolution.which m }
76+
if %w[mongosh mongod].all? { |m| Facter::Util::Resolution.which m }
4577
file = mongod_conf_file
4678
if file
4779
options = get_options_from_config(file)
4880
e = File.exist?('/root/.mongoshrc.js') ? 'load(\'/root/.mongoshrc.js\'); ' : ''
4981

5082
# Check if the mongodb server is responding:
51-
Facter::Core::Execution.exec("mongosh --quiet #{options} --eval \"#{e}EJSON.stringify(db.adminCommand({ ping: 1 }))\"")
83+
Facter::Core::Execution.exec("mongosh --quiet #{options} --eval \"#{e}printjson(db.adminCommand({ ping: 1 }))\"")
5284

5385
if $CHILD_STATUS.success?
5486
Facter::Core::Execution.exec("mongosh --quiet #{options} --eval \"#{e}db.isMaster().ismaster\"")

lib/facter/mongodb_version.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
Facter.add(:mongodb_version) do
44
setcode do
5-
if Facter::Core::Execution.which('mongo')
6-
mongodb_version = Facter::Core::Execution.execute('mongo --version 2>&1')
7-
%r{MongoDB shell version:?\s+v?([\w.]+)}.match(mongodb_version)[1]
5+
if Facter::Core::Execution.which('mongod')
6+
mongodb_version = Facter::Core::Execution.execute('mongod --version 2>&1')
7+
%r{^db version:?\s+v?([\w.]+)}.match(mongodb_version)[1]
88
end
99
end
1010
end

lib/puppet/provider/mongodb.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,24 @@ def self.db_ismaster
143143
cmd_ismaster = 'db.isMaster().ismaster'
144144
cmd_ismaster = mongoshrc_file + cmd_ismaster if mongoshrc_file
145145
db = 'admin'
146-
res = mongosh_cmd(db, conn_string, cmd_ismaster).to_s.split(%r{\n}).last.chomp
147146

148-
# Retry command without authentication when mongorc_file is set and authentication failed
149-
if mongorc_file && res =~ %r{Authentication failed}
150-
res = mongosh_cmd(db, conn_string, 'db.isMaster().ismaster').to_s.chomp
147+
full_command = if mongoshrc_file
148+
mongoshrc_file + cmd_ismaster
149+
else
150+
cmd_ismaster
151+
end
152+
begin
153+
Puppet.debug("MONGODB: In self.db_ismaster with cmd #{cmd_ismaster}")
154+
res = mongosh_cmd(db, conn_string, cmd_ismaster).to_s.split(%r{\n}).last.chomp
155+
rescue StandardError => e
156+
Puppet.debug("MONGODB: In self.db_ismaster in rescue")
157+
if self.auth_enabled && e.message =~ %r{Authentication failed}
158+
res = mongosh_cmd(db, conn_string, 'db.isMaster().ismaster').to_s.chomp
159+
Puppet.debug("MONGODB: In self.db_ismaster with res is #{res}")
160+
end
151161
end
152162

153-
Puppet.debug("In self.db_is_master with res is #{res}")
154-
163+
Puppet.debug("MONGODB: In self.db_is_master with res is #{res}")
155164
res.eql?('true')
156165
end
157166

lib/puppet/provider/mongodb_database/mongodb.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,26 @@ def self.prefetch(resources)
3030
end
3131
end
3232

33+
def auth_enabled
34+
Puppet.debug('MONGODB_DATABASE: in auth_enabled')
35+
self.class.auth_enabled
36+
end
37+
3338
def create
3439
if db_ismaster
35-
out = mongo_eval('db.dummyData.insert({"created_by_puppet": 1})', @resource[:name])
36-
raise "Failed to create DB '#{@resource[:name]}'\n#{out}" if %r{writeError} =~ out
40+
begin
41+
Puppet.debug("MONGODB_DATABASE: In create")
42+
out = mongo_eval('db.dummyData.insertOne({"created_by_puppet": 1})', @resource[:name])
43+
rescue StandardError => e
44+
Puppet.debug("MONGODB_DATABSE: In create in rescue")
45+
if auth_enabled && e.message =~ %r{not authorized on admin to execute commanda} && @resource[:name] == 'admin'
46+
Puppet.warning 'Skipping database creation for admin, need admin user first when security is enabled'
47+
@property_hash[:ensure] = :present
48+
@property_hash[:name] = @resource[:name]
49+
else
50+
raise "Failed to create DB '#{@resource[:name]}'\n#{out}" if %r{writeError} =~ out
51+
end
52+
end
3753
else
3854
Puppet.warning 'Database creation is available only from master host'
3955
end

lib/puppet/provider/mongodb_replset/mongo.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def set_members
289289
return
290290
end
291291

292-
Puppet.debug("REPLICASET DEBUG BIG Is replicaset initiated ? #{rs_initiated?}")
292+
#Puppet.debug("REPLICASET DEBUG BIG Is replicaset initiated ? #{rs_initiated?}")
293293
# When no replicaset is initiated yet, and authenticatoin is anabled,
294294
# mongo_eval still adds the mongorcsh.js. This gives an 'MongoServerError: Authentication failed.' error.
295295
# In this stage, we only can connect to localhost, and only rs.status() and rs.initiate() is possible.

lib/puppet/provider/mongodb_shard/mongo.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
mk_resource_methods
1515

16-
commands mongo: 'mongo'
16+
commands mongosh: 'mongosh'
1717

1818
def initialize(value = {})
1919
super(value)

lib/puppet/provider/mongodb_user/mongodb.rb

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,28 @@
99
def self.instances
1010
require 'json'
1111

12-
if db_ismaster
12+
Puppet.debug("MONGODB_USER self.instances")
13+
14+
#if db_ismaster
1315
script = 'EJSON.stringify(db.system.users.find().toArray())'
1416
# A hack to prevent prefetching failures until admin user is created
1517
script = "try {#{script}} catch (e) { if (e.message.match(/requires authentication/) || e.message.match(/not authorized on admin/)) { 'not authorized on admin' } else {throw e}}" if auth_enabled
1618

1719
out = mongo_eval(script)
18-
Puppet.debug("Result of out in self.instances: #{out}")
19-
Puppet.debug("Type of out in self.instances: #{out.class}")
20-
Puppet.debug("Methods of out in self.instances: #{out.methods}")
21-
Puppet.debug("String of out in self.instances: #{out.to_s} has type #{out.to_s.class}")
22-
Puppet.debug("Json of out in self.instances: #{out.to_json} has type #{out.to_s.class}")
20+
Puppet.debug("MONGODB_USER Result of out in self.instances: #{out}")
21+
Puppet.debug("MONGODB_USER Type of out in self.instances: #{out.class}")
22+
Puppet.debug("MONGODB_USER Methods of out in self.instances: #{out.methods}")
23+
Puppet.debug("MONGODB_USER String of out in self.instances: #{out.to_s} has type #{out.to_s.class}")
24+
Puppet.debug("MONGODB_USER Json of out in self.instances: #{out.to_json} has type #{out.to_s.class}")
2325

2426

27+
Puppet.debug("MONGODB_USER Just before return if auth_enabled and error requires authentication or not authorized on admin")
2528
return [] if auth_enabled && (out.include?('requires authentication') || out.include?('not authorized on admin'))
29+
Puppet.debug("MONGODB_USER after return []")
2630

2731
users = JSON.parse out
28-
Puppet.debug("Result of users in self.instances: #{users}")
29-
Puppet.debug("Type of users in self.instances: #{users.class}")
32+
Puppet.debug("MONGODB_USER Result of users in self.instances: #{users}")
33+
Puppet.debug("MONGODB_USER Type of users in self.instances: #{users.class}")
3034

3135
users.map do |user|
3236
new(name: user['_id'],
@@ -37,14 +41,15 @@ def self.instances
3741
password_hash: user['credentials']['MONGODB-CR'],
3842
scram_credentials: user['credentials']['SCRAM-SHA-1'])
3943
end
40-
else
41-
Puppet.warning 'User info is available only from master host'
42-
[]
43-
end
44+
#else
45+
# Puppet.warning 'User info is available only from master host'
46+
# []
47+
#end
4448
end
4549

4650
# Assign prefetched users based on username and database, not on id and name
4751
def self.prefetch(resources)
52+
Puppet.debug("MONGODB_USER self.prefetch")
4853
users = instances
4954
resources.each do |name, resource|
5055
provider = users.find { |user| user.username == resource[:username] && user.database == resource[:database] }
@@ -55,6 +60,7 @@ def self.prefetch(resources)
5560
mk_resource_methods
5661

5762
def create
63+
Puppet.debug("MONGODB_USER create")
5864
Puppet.debug("In mongodb_user.create. Only works when on the primery node")
5965
if db_ismaster
6066
password_hash = @resource[:password_hash]
@@ -100,14 +106,17 @@ def create
100106
end
101107

102108
def destroy
109+
Puppet.debug("MONGODB_USER destroy")
103110
mongo_eval("db.dropUser(#{@resource[:username].to_json})", @resource[:database])
104111
end
105112

106113
def exists?
114+
Puppet.debug("MONGODB_USER exists?")
107115
!(@property_hash[:ensure] == :absent || @property_hash[:ensure].nil?)
108116
end
109117

110118
def password_hash=(_value)
119+
Puppet.debug("MONGODB_USER password_hash with #{_value}")
111120
if db_ismaster
112121
command = {
113122
updateUser: @resource[:username],
@@ -122,6 +131,7 @@ def password_hash=(_value)
122131
end
123132

124133
def password=(value)
134+
Puppet.debug("MONGODB_USER password= with #{value}")
125135
if mongo_26?
126136
mongo_eval("db.changeUserPassword(#{@resource[:username].to_json}, #{value.to_json})", @resource[:database])
127137
else
@@ -140,6 +150,7 @@ def password=(value)
140150
end
141151

142152
def roles=(roles)
153+
Puppet.debug("MONGODB_USER roles with #{roles}")
143154
if db_ismaster
144155
grant = to_roles(roles, @resource[:database]) - to_roles(@property_hash[:roles], @resource[:database])
145156
mongo_eval("db.getSiblingDB(#{@resource[:database].to_json}).grantRolesToUser(#{@resource[:username].to_json}, #{role_hashes(grant, @resource[:database]).to_json})") unless grant.empty?
@@ -154,6 +165,7 @@ def roles=(roles)
154165
private
155166

156167
def self.from_roles(roles, db)
168+
Puppet.debug("MONGODB_USER self.from_roles with #{roles} and db is #{db}")
157169
roles.map do |entry|
158170
if entry['db'].empty? || entry['db'] == db
159171
entry['role']
@@ -164,6 +176,7 @@ def self.from_roles(roles, db)
164176
end
165177

166178
def to_roles(roles, db)
179+
Puppet.debug("MONGODB_USER self.to_roles with #{roles} and db is #{db}")
167180
roles.map do |entry|
168181
if entry.include? '@'
169182
entry
@@ -174,6 +187,7 @@ def to_roles(roles, db)
174187
end
175188

176189
def role_hashes(roles, db)
190+
Puppet.debug("MONGODB_USER self.role_hashes with #{roles} and db is #{db}")
177191
roles.sort.map do |entry|
178192
if entry.include? '@'
179193
{

0 commit comments

Comments
 (0)