From fdbb0f3a77cf090ee0e88b1930668197fa7f2fa9 Mon Sep 17 00:00:00 2001 From: Jake Spain Date: Fri, 18 Aug 2023 17:36:12 -0400 Subject: [PATCH] Add ability to use bind_as with a service account --- Gemfile.lock | 1 + docs/configuration.md | 12 +++ lib/vmpooler/api/helpers.rb | 30 +++++- spec/unit/api/helpers_spec.rb | 182 ++++++++++++++++++++++------------ vmpooler.yaml.example | 26 +++++ 5 files changed, 185 insertions(+), 66 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b7d1865c..199c5265 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -184,6 +184,7 @@ GEM rspec (~> 3) PLATFORMS + arm64-darwin-22 universal-java-11 x86_64-linux diff --git a/docs/configuration.md b/docs/configuration.md index e577025c..560c3288 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -246,6 +246,18 @@ This can be a string providing a single DN. For multiple DNs please specify the The LDAP object-type used to designate a user object. (optional) +### LDAP\_SERVICE_ACCOUNT\_HASH + +A hash containing the following parameters for a service account to perform the +initial bind. After the initial bind, then a search query is performed using the +'base' and 'user_object', then re-binds as the returned user. + +- :user_dn: The full distinguished name (DN) of the service account used to bind. + +- :password: The password for the service account used to bind. + +(optional) + ### SITE\_NAME The name of your deployment. diff --git a/lib/vmpooler/api/helpers.rb b/lib/vmpooler/api/helpers.rb index c56834af..4669b4c5 100644 --- a/lib/vmpooler/api/helpers.rb +++ b/lib/vmpooler/api/helpers.rb @@ -68,7 +68,7 @@ def authorized? end end - def authenticate_ldap(port, host, encryption_hash, user_object, base, username_str, password_str) + def authenticate_ldap(port, host, encryption_hash, user_object, base, username_str, password_str, service_account_hash = nil) tracer.in_span( "Vmpooler::API::Helpers.#{__method__}", attributes: { @@ -79,6 +79,14 @@ def authenticate_ldap(port, host, encryption_hash, user_object, base, username_s }, kind: :client ) do + if service_account_hash + username = service_account_hash[:user_dn] + password = service_account_hash[:password] + else + username = "#{user_object}=#{username_str},#{base}" + password = password_str + end + ldap = Net::LDAP.new( :host => host, :port => port, @@ -86,12 +94,22 @@ def authenticate_ldap(port, host, encryption_hash, user_object, base, username_s :base => base, :auth => { :method => :simple, - :username => "#{user_object}=#{username_str},#{base}", - :password => password_str + :username => username, + :password => password } ) - return true if ldap.bind + if service_account_hash + return true if ldap.bind_as( + :base => base, + :filter => "(#{user_object}=#{username_str})", + :password => password_str + ) + elsif ldap.bind + return true + else + return false + end return false end @@ -116,6 +134,7 @@ def authenticate(auth, username_str, password_str) :method => :start_tls, :tls_options => { :ssl_version => 'TLSv1' } } + service_account_hash = auth[:ldap]['service_account_hash'] unless ldap_base.is_a? Array ldap_base = ldap_base.split @@ -134,7 +153,8 @@ def authenticate(auth, username_str, password_str) search_user_obj, search_base, username_str, - password_str + password_str, + service_account_hash ) return true if result end diff --git a/spec/unit/api/helpers_spec.rb b/spec/unit/api/helpers_spec.rb index a25ad61b..27176e44 100644 --- a/spec/unit/api/helpers_spec.rb +++ b/spec/unit/api/helpers_spec.rb @@ -268,22 +268,62 @@ class TestHelpers :tls_options => { :ssl_version => 'TLSv1' } } end - it 'should attempt ldap authentication' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str) - subject.authenticate(auth, username_str, password_str) - end + context 'without a service account' do + it 'should attempt ldap authentication' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, nil) + + subject.authenticate(auth, username_str, password_str) + end - it 'should return true when authentication is successful' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str).and_return(true) + it 'should return true when authentication is successful' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, nil).and_return(true) - expect(subject.authenticate(auth, username_str, password_str)).to be true + expect(subject.authenticate(auth, username_str, password_str)).to be true + end + + it 'should return false when authentication fails' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, nil).and_return(false) + + expect(subject.authenticate(auth, username_str, password_str)).to be false + end end - it 'should return false when authentication fails' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str).and_return(false) + context 'with a service account' do + let(:service_account_hash) do + { + :user_dn => 'cn=Service Account,ou=users,dc=example,dc=com', + :password => 's3cr3t' + } + end + let(:auth) { + { + 'provider' => 'ldap', + ldap: { + 'host' => host, + 'base' => base, + 'user_object' => user_object, + 'service_account_hash' => service_account_hash + } + } + } + it 'should attempt ldap authentication' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, service_account_hash) + + subject.authenticate(auth, username_str, password_str) + end + + it 'should return true when authentication is successful' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, service_account_hash).and_return(true) + + expect(subject.authenticate(auth, username_str, password_str)).to be true + end + + it 'should return false when authentication fails' do + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base, username_str, password_str, service_account_hash).and_return(false) - expect(subject.authenticate(auth, username_str, password_str)).to be false + expect(subject.authenticate(auth, username_str, password_str)).to be false + end end context 'with an alternate ssl_version' do @@ -298,7 +338,7 @@ class TestHelpers end it 'should specify the alternate ssl_version when authenticating' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, secure_encryption, user_object, base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, secure_encryption, user_object, base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end @@ -311,7 +351,7 @@ class TestHelpers end it 'should specify the alternate port when authenticating' do - expect(subject).to receive(:authenticate_ldap).with(alternate_port, host, default_encryption, user_object, base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(alternate_port, host, default_encryption, user_object, base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end @@ -331,7 +371,7 @@ class TestHelpers end it 'should specify the secure port and encryption options when authenticating' do - expect(subject).to receive(:authenticate_ldap).with(secure_port, host, secure_encryption, user_object, base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(secure_port, host, secure_encryption, user_object, base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end @@ -349,36 +389,36 @@ class TestHelpers end it 'should attempt to bind with each base' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should not search the second base when the first binds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str).and_return(true) - expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str, nil).and_return(true) + expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should search the second base when the first bind fails' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should return true when any bind succeeds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str).and_return(true) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str, nil).and_return(true) expect(subject.authenticate(auth, username_str, password_str)).to be true end it 'should return false when all bind attempts fail' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object, base[1], username_str, password_str, nil).and_return(false) expect(subject.authenticate(auth, username_str, password_str)).to be false end @@ -396,36 +436,36 @@ class TestHelpers end it 'should attempt to bind with each user object' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should not search the second user object when the first binds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str).and_return(true) - expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str, nil).and_return(true) + expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should search the second user object when the first bind fails' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should return true when any bind succeeds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str).and_return(true) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str, nil).and_return(true) expect(subject.authenticate(auth, username_str, password_str)).to be true end it 'should return false when all bind attempts fail' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base, username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base, username_str, password_str, nil).and_return(false) expect(subject.authenticate(auth, username_str, password_str)).to be false end @@ -450,64 +490,64 @@ class TestHelpers end it 'should attempt to bind with each user object and base' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should not continue searching when the first combination binds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(true) - expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str) - expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str) - expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(true) + expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil) + expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil) + expect(subject).to_not receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should search the remaining combinations when the first bind fails' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should search the remaining combinations when the first two binds fail' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should search the remaining combination when the first three binds fail' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil) subject.authenticate(auth, username_str, password_str) end it 'should return true when any bind succeeds' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str).and_return(true) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil).and_return(true) expect(subject.authenticate(auth, username_str, password_str)).to be true end it 'should return false when all bind attempts fail' do - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str).and_return(false) - expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[0], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[0], base[1], username_str, password_str, nil).and_return(false) + expect(subject).to receive(:authenticate_ldap).with(default_port, host, default_encryption, user_object[1], base[1], username_str, password_str, nil).and_return(false) expect(subject.authenticate(auth, username_str, password_str)).to be false end @@ -541,6 +581,12 @@ class TestHelpers :tls_options => { :ssl_version => 'TLSv1' } } end + let(:service_account_hash) do + { + :user_dn => 'cn=Service Account,ou=users,dc=example,dc=com', + :password => 's3cr3t' + } + end let(:ldap) { double('ldap') } it 'should create a new ldap connection' do allow(ldap).to receive(:bind) @@ -572,6 +618,20 @@ class TestHelpers expect(subject.authenticate_ldap(port, host, encryption, user_object, base, username_str, password_str)).to be false end + + it 'should return true when a bind_as is successful' do + expect(Net::LDAP).to receive(:new).and_return(ldap) + expect(ldap).to receive(:bind_as).and_return(true) + + expect(subject.authenticate_ldap(port, host, encryption, user_object, base, username_str, password_str, service_account_hash)).to be true + end + + it 'should return false when a bind_as fails' do + expect(Net::LDAP).to receive(:new).and_return(ldap) + expect(ldap).to receive(:bind_as).and_return(false) + + expect(subject.authenticate_ldap(port, host, encryption, user_object, base, username_str, password_str, service_account_hash)).to be false + end end end diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example index efd0ba7b..d1024c2f 100644 --- a/vmpooler.yaml.example +++ b/vmpooler.yaml.example @@ -367,6 +367,15 @@ # - user_object # The LDAP object-type used to designate a user object. # +# - service_account_hash +# A hash containing the following parameters for a service account to perform the +# initial bind. After the initial bind, then a search query is performed using the +# 'base' and 'user_object', then re-binds as the returned user. +# - :user_dn +# The full distinguished name (DN) of the service account used to bind. +# - :password +# The password for the service account used to bind. +# # Example: # :auth: # provider: 'ldap' @@ -375,6 +384,23 @@ # port: 389 # base: 'ou=users,dc=company,dc=com' # user_object: 'uid' +# +# :auth: +# provider: 'ldap' +# :ldap: +# host: 'ldap.example.com' +# port: 636 +# service_account_hash: +# :user_dn: 'cn=Service Account,ou=Accounts,dc=ldap,dc=example,dc=com' +# :password: 'service-account-password' +# encryption: +# :method: :simple_tls +# :tls_options: +# :ssl_version: 'TLSv1_2' +# base: +# - 'ou=Accounts,dc=company,dc=com' +# user_object: +# - 'samAccountName' :auth: provider: 'ldap'