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

rebased PR#308 to allow other databases in the format 'role@db' for mongodb_user #432

Merged
merged 1 commit into from
Feb 10, 2020
Merged
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
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,10 @@ For more information please refer to [MongoDB Authentication Process](http://doc
Plain-text user password (will be hashed)

##### `roles`
Array with user roles. Default: ['dbAdmin']
Array with user roles as string.
Roles will be granted to user's database if no alternative database is explicitly defined.
Example: ['dbAdmin', 'readWrite@other_database']
Default: ['dbAdmin']

### Providers

Expand Down Expand Up @@ -697,7 +700,10 @@ Plaintext password of the user.
Name of database. It will be created, if not exists.

##### `roles`
Array with user roles. Default: ['dbAdmin']
Array with user roles as string.
Roles will be granted to user's database if no alternative database is explicitly defined.
Example: ['dbAdmin', 'readWrite@other_database']
Default: ['dbAdmin']

##### `tries`
The maximum amount of two second tries to wait MongoDB startup. Default: 10
Expand Down
38 changes: 32 additions & 6 deletions lib/puppet/provider/mongodb_user/mongodb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def create
customData: {
createdBy: "Puppet Mongodb_user['#{@resource[:name]}']"
},
roles: @resource[:roles],
roles: role_hashes(@resource[:roles], @resource[:database]),
digestPassword: false
}

Expand Down Expand Up @@ -110,14 +110,14 @@ def password=(value)

def roles=(roles)
if db_ismaster
grant = roles - @property_hash[:roles]
grant = to_roles(roles, @resource[:database]) - to_roles(@property_hash[:roles], @resource[:database])
unless grant.empty?
mongo_eval("db.getSiblingDB(#{@resource[:database].to_json}).grantRolesToUser(#{@resource[:username].to_json}, #{grant.to_json})")
mongo_eval("db.getSiblingDB(#{@resource[:database].to_json}).grantRolesToUser(#{@resource[:username].to_json}, #{role_hashes(grant, @resource[:database]).to_json})")
end

revoke = @property_hash[:roles] - roles
revoke = to_roles(@property_hash[:roles], @resource[:database]) - to_roles(roles, @resource[:database])
unless revoke.empty?
mongo_eval("db.getSiblingDB(#{@resource[:database].to_json}).revokeRolesFromUser(#{@resource[:username].to_json}, #{revoke.to_json})")
mongo_eval("db.getSiblingDB(#{@resource[:database].to_json}).revokeRolesFromUser(#{@resource[:username].to_json}, #{role_hashes(revoke, @resource[:database]).to_json})")
end
else
Puppet.warning 'User roles operations are available only from master host'
Expand All @@ -128,11 +128,37 @@ def roles=(roles)

def self.from_roles(roles, db)
roles.map do |entry|
if entry['db'] == db
if entry['db'].empty? || entry['db'] == db
entry['role']
else
"#{entry['role']}@#{entry['db']}"
end
end.sort
end

def to_roles(roles, db)
roles.map do |entry|
if entry.include? '@'
entry
else
"#{entry}@#{db}"
end
end
end

def role_hashes(roles, db)
roles.sort.map do |entry|
if entry.include? '@'
{
'role' => entry.gsub(%r{^(.*)@.*$}, '\1'),
'db' => entry.gsub(%r{^.*@(.*)$}, '\1')
}
else
{
'role' => entry,
'db' => db
}
end
end
end
end
2 changes: 1 addition & 1 deletion lib/puppet/type/mongodb_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def initialize(*args)
newproperty(:roles, array_matching: :all) do
desc "The user's roles."
defaultto ['dbAdmin']
newvalue(%r{^\w+$})
newvalue(%r{^\w+(@\w+)?$})

# Pretty output for arrays.
def should_to_s(value)
Expand Down
78 changes: 78 additions & 0 deletions spec/acceptance/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,82 @@ class { 'mongodb::server': port => 27018 }
end
end
end

context 'with the basic roles syntax' do
it 'compiles with no errors' do
pp = <<-EOS
class { 'mongodb::server': }
-> class { 'mongodb::client': }
-> mongodb_database { 'testdb': ensure => present }
->
mongodb_user {'testuser':
ensure => present,
password_hash => mongodb_password('testuser', 'passw0rd'),
database => 'testdb',
roles => ['readWrite', 'dbAdmin'],
}
EOS

apply_manifest(pp, catch_failures: true)
apply_manifest(pp, catch_changes: true)
end

it 'creates the user' do
shell("mongo testdb --quiet --eval 'db.auth(\"testuser\",\"passw0rd\")'") do |r|
expect(r.stdout.chomp).to eq('1')
end
end
end

context 'with the new multidb role syntax' do
it 'compiles with no errors' do
pp = <<-EOS
class { 'mongodb::server': }
-> class { 'mongodb::client': }
-> mongodb_database { 'testdb': ensure => present }
-> mongodb_database { 'testdb2': ensure => present }
->
mongodb_user {'testuser':
ensure => present,
password_hash => mongodb_password('testuser', 'passw0rd'),
database => 'testdb',
roles => ['readWrite', 'dbAdmin'],
}
->
mongodb_user {'testuser2':
ensure => present,
password_hash => mongodb_password('testuser2', 'passw0rd'),
database => 'testdb2',
roles => ['readWrite', 'dbAdmin', 'readWrite@testdb', 'dbAdmin@testdb'],
}
EOS

apply_manifest(pp, catch_failures: true)
apply_manifest(pp, catch_changes: true)
end

it 'allows the testuser' do
shell("mongo testdb --quiet --eval 'db.auth(\"testuser\",\"passw0rd\")'") do |r|
expect(r.stdout.chomp).to eq('1')
end
end

it 'assigns roles to testuser' do
shell("mongo testdb --quiet --eval 'db.auth(\"testuser\",\"passw0rd\"); db.getUser(\"testuser\")[\"roles\"].forEach(function(role){print(role.role + \"@\" + role.db)})'") do |r|
expect(r.stdout.split(%r{\n})).to contain_exactly('readWrite@testdb', 'dbAdmin@testdb')
end
end

it 'allows the second user to connect to its default database' do
shell("mongo testdb2 --quiet --eval 'db.auth(\"testuser2\",\"passw0rd\")'") do |r|
expect(r.stdout.chomp).to eq('1')
end
end

it 'assigns roles to testuser2' do
shell("mongo testdb2 --quiet --eval 'db.auth(\"testuser2\",\"passw0rd\"); db.getUser(\"testuser2\")[\"roles\"].forEach(function(role){print(role.role + \"@\" + role.db)})'") do |r|
expect(r.stdout.split(%r{\n})).to contain_exactly('readWrite@testdb2', 'dbAdmin@testdb2', 'readWrite@testdb', 'dbAdmin@testdb')
end
end
end
end
32 changes: 16 additions & 16 deletions spec/unit/puppet/provider/mongodb_user/mongodb_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
describe Puppet::Type.type(:mongodb_user).provider(:mongodb) do
let(:raw_users) do
[
{ '_id' => 'admin.root', 'user' => 'root', 'db' => 'admin', 'credentials' => { 'MONGODB-CR' => 'pass', 'SCRAM-SHA-1' => { 'iterationCount' => 10_000, 'salt' => 'salt', 'storedKey' => 'storedKey', 'serverKey' => 'serverKey' } }, 'roles' => [{ 'role' => 'role2', 'db' => 'admin' }, { 'role' => 'role1', 'db' => 'admin' }] }
{ '_id' => 'admin.root', 'user' => 'root', 'db' => 'admin', 'credentials' => { 'MONGODB-CR' => 'pass', 'SCRAM-SHA-1' => { 'iterationCount' => 10_000, 'salt' => 'salt', 'storedKey' => 'storedKey', 'serverKey' => 'serverKey' } }, 'roles' => [{ 'role' => 'role2', 'db' => 'admin' }, { 'role' => 'role3', 'db' => 'user_database' }, { 'role' => 'role1', 'db' => 'admin' }] }
].to_json
end

Expand All @@ -17,7 +17,7 @@
name: 'new_user',
database: 'new_database',
password_hash: 'pass',
roles: %w[role1 role2],
roles: %w[role1 role2@other_database],
provider: described_class.name
)
end
Expand Down Expand Up @@ -56,7 +56,7 @@
"createUser":"new_user",
"pwd":"pass",
"customData":{"createdBy":"Puppet Mongodb_user['new_user']"},
"roles":["role1","role2"],
"roles":[{"role":"role1","db":"new_database"},{"role":"role2","db":"other_database"}],
"digestPassword":false
}
EOS
Expand Down Expand Up @@ -114,40 +114,40 @@

describe 'roles' do
it 'returns a sorted roles' do
expect(instance.roles).to eq(%w[role1 role2])
expect(instance.roles).to eq(%w[role1 role2 role3@user_database])
end
end

describe 'roles=' do
it 'changes nothing' do
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2])
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2@other_database])
expect(provider).not_to receive(:mongo_eval)
provider.roles = %w[role1 role2]
provider.roles = %w[role1 role2@other_database]
end

it 'grant a role' do
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2])
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2@other_database])
expect(provider).to receive(:mongo_eval).
with('db.getSiblingDB("new_database").grantRolesToUser("new_user", ["role3"])')
provider.roles = %w[role1 role2 role3]
with('db.getSiblingDB("new_database").grantRolesToUser("new_user", [{"role":"role3","db":"new_database"}])')
provider.roles = %w[role1 role2@other_database role3]
end

it 'revokes a role' do
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2])
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2@other_database])
expect(provider).to receive(:mongo_eval).
with('db.getSiblingDB("new_database").revokeRolesFromUser("new_user", ["role1"])')
provider.roles = ['role2']
with('db.getSiblingDB("new_database").revokeRolesFromUser("new_user", [{"role":"role1","db":"new_database"}])')
provider.roles = ['role2@other_database']
end

# rubocop:disable RSpec/MultipleExpectations
it 'exchanges a role' do
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2])
resource.provider.set(name: 'new_user', ensure: :present, roles: %w[role1 role2@other_database])
expect(provider).to receive(:mongo_eval).
with('db.getSiblingDB("new_database").revokeRolesFromUser("new_user", ["role1"])')
with('db.getSiblingDB("new_database").revokeRolesFromUser("new_user", [{"role":"role1","db":"new_database"}])')
expect(provider).to receive(:mongo_eval).
with('db.getSiblingDB("new_database").grantRolesToUser("new_user", ["role3"])')
with('db.getSiblingDB("new_database").grantRolesToUser("new_user", [{"role":"role3","db":"new_database"}])')

provider.roles = %w[role2 role3]
provider.roles = %w[role2@other_database role3]
end
# rubocop:enable RSpec/MultipleExpectations
end
Expand Down