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

draft: add x509 auth for admin user using mongosh #668

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 30 additions & 5 deletions lib/facter/is_master.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,23 @@ def mongod_conf_file
locations.find { |location| File.exist? location }
end

def mongosh_conf_file
'/root/.mongosh.yaml' if File.exist?('/root/mongosh.yaml')
end

def get_options_from_hash_config(config)
# read also the mongoshrc.yaml yaml file, to retrieve the admins certkey file
if mongosh_conf_file
mongosh_config = YAML.load_file(mongosh_conf_file)
# check which tlscert we need to use
_tlscert = if config['setParameter'] && config['setParameter']['authenticationMechanisms'] == 'MONGODB-X509' &&
mongosh_config['admin'] && mongosh_config['admin']['tlsCertificateKeyFile']
mongosh_config['admin']['tlsCertificateKeyFile']
end
else
_tlscert = config['net.tls.certificateKeyFile']
end

result = []

result << "--port #{config['net.port']}" unless config['net.port'].nil?
Expand All @@ -21,15 +37,21 @@ def get_options_from_hash_config(config)
# - tlsMode is "requireTLS"
# - Parameter --tlsCertificateKeyFile is set
# - Parameter --tlsCAFile is set
result << "--tls --host #{Facter.value(:fqdn)}" if config['net.tls.mode'] == 'requireTLS' || !config['net.tls.certificateKeyFile'].nil? || !config['net.tls.CAFile'].nil?
result << "--tlsCertificateKeyFile #{config['net.tls.certificateKeyFile']}" unless config['net.tls.certificateKeyFile'].nil?
result << "--tls --host #{Facter.value(:fqdn)}" if config['net.tls.mode'] == 'requireTLS' || !_tlscert.nil? || !config['net.tls.CAFile'].nil?
result << "--tlsCertificateKeyFile #{_tlscert}" unless _tlscert.nil?
result << "--tlsCAFile #{config['net.tls.CAFile']}" unless config['net.tls.CAFile'].nil?

# use --authenticationMechanism, ---authenticationDatabase
# when
# - authenticationMechanism MONGODB-X509
result << "--authenticationDatabase '$external' --authenticationMechanism MONGODB-X509" unless config['setParameter'].nil? || config['setParameter']['authenticationMechanisms'] != 'MONGODB-X509'

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

result.join(' ')
end

# I think we can get rid of this ?
def get_options_from_keyvalue_config(file)
config = {}
File.readlines(file).map do |line|
Expand Down Expand Up @@ -72,16 +94,19 @@ def get_options_from_config(file)
Facter.add('mongodb_is_master') do
setcode do
if %w[mongo mongod].all? { |m| Facter::Util::Resolution.which m }
# if we have a mongod_conf_file we use mongosh, else stick to mongo
# this will only work for < 6.x versions
file = mongod_conf_file
if file
options = get_options_from_config(file)
e = File.exist?('/root/.mongorc.js') ? 'load(\'/root/.mongorc.js\'); ' : ''
e = File.exist?('/root/.mongoshrc.js') ? 'load(\'/root/.mongoshrc.js\'); ' : ''

# Check if the mongodb server is responding:
Facter::Core::Execution.exec("mongo --quiet #{options} --eval \"#{e}printjson(db.adminCommand({ ping: 1 }))\"")
Facter::Core::Execution.exec("mongosh --quiet #{options} --eval \"#{e}printjson(db.adminCommand({ ping: 1 }))\"")

if $CHILD_STATUS.success?
Facter::Core::Execution.exec("mongo --quiet #{options} --eval \"#{e}db.isMaster().ismaster\"")
# TODO: need to catch errors like 'Error: Authentication failed.' ?
Facter::Core::Execution.exec("mongosh --quiet #{options} --eval \"#{e}db.isMaster().ismaster\"")
else
'not_responding'
end
Expand Down
33 changes: 27 additions & 6 deletions lib/puppet/provider/mongodb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
class Puppet::Provider::Mongodb < Puppet::Provider
# Without initvars commands won't work.
initvars
commands mongo: 'mongo'
# TODO: do we still need to support mongo ? Since it is removed from 6.x, not in this PR
commands mongo: 'mongosh'

# Optional defaults file
def self.mongorc_file
"load('#{Facter.value(:root_home)}/.mongorc.js'); " if File.file?("#{Facter.value(:root_home)}/.mongorc.js")
"load('#{Facter.value(:root_home)}/.mongoshrc.js'); " if File.file?("#{Facter.value(:root_home)}/.mongoshrc.js")
end

def mongorc_file
Expand All @@ -26,7 +27,16 @@ def self.mongod_conf_file
end

def self.mongo_conf
mongosh_config = YAML.load_file('/root/.mongosh.yaml') || {}
config = YAML.load_file(mongod_conf_file) || {}
# determine if we need the tls for connecion or client
_tlscert = if config['setParameter'] && config['setParameter']['authenticationMechanisms'] == 'MONGODB-X509'
if mongosh_config['admin'] && mongosh_config['admin']['tlsCertificateKeyFile']
mongosh_config['admin']['tlsCertificateKeyFile']
else
config['net.tls.certificateKeyFile']
end
end
{
'bindip' => config['net.bindIp'],
'port' => config['net.port'],
Expand All @@ -37,11 +47,11 @@ def self.mongo_conf
'sslca' => config['net.ssl.CAFile'],
'tlsallowInvalidHostnames' => config['net.tls.allowInvalidHostnames'],
'tls' => config['net.tls.mode'],
'tlscert' => config['net.tls.certificateKeyFile'],
'tlscert' => _tlscert,
'tlsca' => config['net.tls.CAFile'],
'auth' => config['security.authorization'],
'shardsvr' => config['sharding.clusterRole'],
'confsvr' => config['sharding.clusterRole']
'confsvr' => config['sharding.clusterRole'],
}
end

Expand Down Expand Up @@ -90,15 +100,16 @@ def self.mongo_cmd(db, host, cmd)

if tls_is_enabled(config)
args.push('--tls')
args += ['--tlsCertificateKeyFile', config['tlscert']]

tls_ca = config['tlsca']
args += ['--tlsCAFile', tls_ca] unless tls_ca.nil?

args += ['--tlsCertificateKeyFile', config['tlscert']]

args.push('--tlsAllowInvalidHostnames') if tls_invalid_hostnames(config)
end

args += ['--eval', cmd]
args += ['--eval', "\"#{cmd}\""]
mongo(args)
end

Expand Down Expand Up @@ -192,6 +203,7 @@ def mongo_version
self.class.mongo_version
end

# TODO: moingosh only from 4.2 bersion ?, so do we remove this?
def self.mongo_26?
v = mongo_version
!v[%r{^2\.6\.}].nil?
Expand All @@ -218,4 +230,13 @@ def self.mongo_5?
def mongo_5?
self.class.mongo_5?
end

def self.mongo_6?
v = mongo_version
!v[%r{^6\.}].nil?
end

def mongo_6?
self.class.mongo_6?
end
end
8 changes: 6 additions & 2 deletions lib/puppet/provider/mongodb_replset/mongo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def members=(hosts)

def self.instances
instance = replset_properties
Puppet.debug("In replset.instances with #{instance}")
if instance
# There can only be one replset per node
[new(instance)]
Expand All @@ -39,6 +40,7 @@ def self.instances
end

def self.prefetch(resources)
Puppet.debug("In replset.prefetch")
instances.each do |prov|
resource = resources[prov.name]
resource.provider = prov if resource
Expand Down Expand Up @@ -147,6 +149,7 @@ def self.replset_properties
end

def get_hosts_status(members)
Puppet.debug("In get_hosts_status")
alive = []
members.select do |member|
begin
Expand Down Expand Up @@ -189,6 +192,7 @@ def get_hosts_status(members)
end

def get_members_changes(current_members_conf, new_members_conf)
Puppet.debug("In get_members_changes")
# no changes in members config
return [[], [], []] if new_members_conf.nil?

Expand Down Expand Up @@ -284,7 +288,7 @@ def set_members
Puppet.debug 'Replica set initialization has successfully ended'
return true
else
Puppet.debug "Wainting for replica initialization. Retry: #{n}"
Puppet.debug "Waiting for replica initialization. Retry: #{n}"
sleep retry_sleep
next
end
Expand Down Expand Up @@ -388,7 +392,7 @@ def mongo_command(command, host, retries = 4)

def self.mongo_command(command, host = nil, retries = 4)
begin
output = mongo_eval("printjson(#{command})", 'admin', retries, host)
output = mongo_eval("EJSON.stringify(#{command})", 'admin', retries, host)
rescue Puppet::ExecutionFailure => e
Puppet.debug "Got an exception: #{e}"
raise
Expand Down
23 changes: 15 additions & 8 deletions lib/puppet/provider/mongodb_user/mongodb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
defaultfor kernel: 'Linux'

def self.instances
# TODO: add supprt for x509 client certs
require 'json'

if db_ismaster
Expand Down Expand Up @@ -59,27 +60,31 @@ def create
roles: role_hashes(@resource[:roles], @resource[:database]),
}

if mongo_4? || mongo_5?
if @resource[:auth_mechanism] == :scram_sha_256
if mongo_4? || mongo_5? || mongo_6?
case @resource[:auth_mechanism]
when :scram_sha_256
command[:mechanisms] = ['SCRAM-SHA-256']
command[:pwd] = @resource[:password]
command[:digestPassword] = true
else
when :scram_sha_1
command[:mechanisms] = ['SCRAM-SHA-1']
command[:pwd] = password_hash
command[:digestPassword] = false
when :x509
command[:mechanisms] = ['MONGODB-X509']
else
command[:pwd] = password_hash
command[:digestPassword] = false
end
else
command[:pwd] = password_hash
command[:digestPassword] = false
end

Puppet.debug("create a user: db.runCommand(#{command.to_json}), #{@resource[:database]}")
mongo_eval("db.runCommand(#{command.to_json})", @resource[:database])
else
Puppet.warning 'User creation is available only from master host'

@property_hash[:ensure] = :present
@property_hash[:username] = @resource[:username]
# TODO: enforce '$external$' database here when x509, or in the manifest ?
@property_hash[:database] = @resource[:database]
@property_hash[:password_hash] = ''
@property_hash[:roles] = @resource[:roles]
Expand Down Expand Up @@ -111,6 +116,7 @@ def password_hash=(_value)
end

def password=(value)
# TODO: remove since mongosh not available in this version ?
if mongo_26?
mongo_eval("db.changeUserPassword(#{@resource[:username].to_json}, #{value.to_json})", @resource[:database])
else
Expand All @@ -120,7 +126,8 @@ def password=(value)
digestPassword: true
}

if mongo_4? || mongo_5?
if mongo_4? || mongo_5? || mongo_6?
# TODO: x509 doesnt use password, how to handle ?
command[:mechanisms] = @resource[:auth_mechanism] == :scram_sha_256 ? ['SCRAM-SHA-256'] : ['SCRAM-SHA-1']
end

Expand Down
2 changes: 2 additions & 0 deletions lib/puppet/type/mongodb_replset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
desc 'The replicaSet settings config'

def insync?(is)
Puppet.debug("in type replset attribute settings with #{is}")
should.each do |k, v|
if v != is[k]
Puppet.debug 'The replicaset settings config is not insync'
Expand All @@ -50,6 +51,7 @@ def insync?(is)

# check if is different
def insync?(is)
Puppet.debug("in type replset attribute members #{is}")
sync = true
current = is.clone
should.each do |sm|
Expand Down
18 changes: 10 additions & 8 deletions lib/puppet/type/mongodb_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def to_s?(value)
newproperty(:password_hash) do
desc 'The password hash of the user. Use mongodb_password() for creating hash. Only available on MongoDB 3.0 and later. SCRAM-SHA-256 authentication mechanism is not supported.'
defaultto do
if @resource[:password].nil?
if @resource[:auth_mechanism] != :x509 && @resource[:password].nil?
raise Puppet::Error, "Property 'password_hash' must be set. Use mongodb_password() for creating hash." if provider.database == :absent
end
end
Expand Down Expand Up @@ -99,7 +99,7 @@ def insync?(_is)
newparam(:auth_mechanism) do
desc 'Authentication mechanism. Password verification is not supported with SCRAM-SHA-256.'
defaultto :scram_sha_1
newvalues(:scram_sha_256, :scram_sha_1)
newvalues(:scram_sha_256, :scram_sha_1, :x509)
end

newparam(:update_password, boolean: true) do
Expand All @@ -124,12 +124,14 @@ def insync?(_is)
end

validate do
if self[:password_hash].nil? && self[:password].nil? && provider.password.nil? && provider.password_hash.nil?
err("Either 'password_hash' or 'password' should be provided")
elsif !self[:password_hash].nil? && !self[:password].nil?
err("Only one of 'password_hash' or 'password' should be provided")
elsif !self[:password_hash].nil? && self[:auth_mechanism] == :scram_sha_256
err("'password_hash' is not supported with SCRAM-SHA-256 authentication mechanism")
if self[:auth_mechanism] != :x509
if self[:password_hash].nil? && self[:password].nil? && provider.password.nil? && provider.password_hash.nil?
err("Either 'password_hash' or 'password' should be provided")
elsif !self[:password_hash].nil? && !self[:password].nil?
err("Only one of 'password_hash' or 'password' should be provided")
elsif !self[:password_hash].nil? && self[:auth_mechanism] == :scram_sha_256
err("'password_hash' is not supported with SCRAM-SHA-256 authentication mechanism")
end
end
if should(:scram_credentials)
raise("The parameter 'scram_credentials' is read-only and cannot be changed")
Expand Down
38 changes: 21 additions & 17 deletions manifests/db.pp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#
define mongodb::db (
String $user,
Enum['scram_sha_1', 'scram_sha_256'] $auth_mechanism = 'scram_sha_1',
Enum['scram_sha_1', 'scram_sha_256', 'x509'] $auth_mechanism = 'scram_sha_1',
String $db_name = $name,
Optional[Variant[String[1], Sensitive[String[1]]]] $password_hash = undef,
Optional[Variant[String[1], Sensitive[String[1]]]] $password = undef,
Expand All @@ -29,25 +29,29 @@
tries => $tries,
}

if $password_hash =~ Sensitive[String] {
$hash = $password_hash.unwrap
} elsif $password_hash {
$hash = $password_hash
} elsif $password {
$hash = mongodb_password($user, $password)
} else {
fail("Parameter 'password_hash' or 'password' should be provided to mongodb::db.")
}
if $auth_mechanism != 'x509' {
if $password_hash =~ Sensitive[String] {
$hash = $password_hash.unwrap
} elsif $password_hash {
$hash = $password_hash
} elsif $password {
$hash = mongodb_password($user, $password)
} else {
fail("Parameter 'password_hash' or 'password' should be provided to mongodb::db.")
}

if $auth_mechanism == 'scram_sha_256' {
$password_config = {
password => $password,
update_password => $update_password,
if $auth_mechanism == 'scram_sha_256' {
$password_config = {
password => $password,
update_password => $update_password,
}
} else {
$password_config = {
password_hash => $hash,
}
}
} else {
$password_config = {
password_hash => $hash,
}
$password_config = {}
}

mongodb_user { "User ${user} on db ${db_name}":
Expand Down
Loading