In my test environment, our product ldap server is inaccessible due to security issues. But, we need to verify ldap login for gitlab. So, we introduce a simple ldap server setup here.
Basically, there is already a ruby ldap server available in gem here, what we need to do is to configure and bring it up. here, it will be set up with chef recipe.
ruby-ldapserver gem installation
gem_package 'ruby-ldapserver' do
action :install
end
ldap_server.rb
So, we will simply adapt the example script called rbslapd3.rb from ruby-ldapserver and add our customization, like port, certicate, namingContexts.
....
s = LDAP::Server.new(
port: 636,
nodelay: true,
listen: 10,
ssl_key_file: 'my.key',
ssl_cert_file: 'my.crt',
ssl_on_connect: true,
operation_class: DirOperation,
operation_args: [directory],
schema: schema,
namingContexts: ['dc=ldap,dc=example,dc=com']
)
# s.run_prefork
s.run_tcpserver
s.join
...
As test env, a self-signed certificate will suffice and can be generated by
#=> openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out my.crt -keyout my.key
core.schema
schema is vital for ldap server to understand/recognize what consist of ldap content. Here, we will continue utilize the core.schema from ruby-ldapserver, adding a few entries neccessary to our ldap server. You can/should add more according to your need.
objectclass ( 2.5.13.2 NAME 'User'
SUP person STRUCTURAL
MUST ( uid $ c $ mail ) )
attributetype ( 1.2.840.113556.1.4.221
NAME 'sAMAccountName'
DESC 'sAMAccountName'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
attributetype ( 1.2.840.113556.1.4.656
NAME 'userPrincipalName'
DESC 'userPrincipalName'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
attributetype ( 1.2.840.113556.1.2.102
NAME 'memberOf'
DESC 'memberOf'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
attributetype ( 2.5.4.49
NAME ('dn' 'distinguishedName' )
EQUALITY distinguishedNameMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
Add test entry to ldap server
As you can see, all the attributes for ldap server to add, need to be defined in the schema. For example, the objectclass User is self-defined in the schema previously.
ruby_block 'create one test entry for ldap server' do
block do
require 'net/ldap'
# ldap client instance
con = Net::LDAP.new(host: 'localhost',
port: 636,
base: 'ou=ldap,o=example.com',
encryption: :simple_tls)
# username and password, empty for simplicity
con.auth '', ''
# authenticate with the server
con.bind
dn = "uid=123456,c=us,ou=ldap,o=example.com"
# attributes to be added
attr = {
uid: "123456",
cn: 'Roger Whatever',
objectclass: 'User',
sn: 'Whatever',
mail: 'Whatever@us.example.com',
c: 'us'
}
# execute the addition
con.add(dn: dn, attributes: attr)
end
end
query against ldap server
Query looks quite similar with add:
require 'net/ldap'
con = Net::LDAP.new(:host => 'localhost',
:port => 636,
:base => 'ou=ldap,o=example.com',
:encryption => :simple_tls)
con.auth '', ''
con.bind
filter = Net::LDAP::Filter.eq("uid", "123456")
con.search(filter:filter)
outputs looks like:
[#<Net::LDAP::Entry:0x00000000021595f8 @myhash={:dn=>["uid=123456,c=us,ou=ldap,o=example.com"], :uid=>["123456"], :cn=>["Roger Whatever"], :objectclass=>["User", "person", "top"], :sn=>["Whatever"], :mail=>["Whatever@us.example.com"], :c=>["us"]}>]
Verify with gitlab rake task
In order to verify it works for gitlab, gitlab-rake gitlab:ldap:check must be run to check.
#=> gitlab-rake gitlab:ldap:check --trace
** Invoke gitlab:ldap:check (first_time)
** Invoke gitlab_environment (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute gitlab_environment
** Execute gitlab:ldap:check
Checking LDAP ...
Server: ldapmain
not verifying SSL hostname of LDAPS server 'ldap.example.com:636'
LDAP authentication... Success
LDAP users with access to your GitLab server (only showing the first 100 results)
DN: uid=123456,c=us,ou=ldap,o=example.com mail: Whatever@us.example.com
Checking LDAP ... Finished
So, it works fine.
Please note that, gitlab:ldap:check actually need attributetype ["dn", "mail", "cn", "email", "userPrincipalName", "uid", "sAMAccountName", "userid", "memberof"] to be available in the schema. If they are undefined, it will throw error. That is why we define these attributetype in core.schema in first place.
The actual query ran by gitlab:ldap:check is as below:
options = {:attributes=>["dn", "mail", "cn", "email", "userPrincipalName", "uid", "sAMAccountName", "userid", "memberof"], :base=>"ou=ldap,o=example.com", :paged_searches_supported=>false}
ldap.search(options)
Full code
package 'ruby'
gem_package 'ruby-ldapserver' do
action :install
end
# require 'net/ldap'
chef_gem 'net-ldap'
directory '/opt/ldap-server/bin' do
recursive true
end
%w(crt key).each do |file_extension|
cookbook_file "my.#{file_extension}" do
source "ldap/my.#{file_extension}"
path "/opt/ldap-server/my.#{file_extension}"
end
end
cookbook_file 'ldap_server.rb' do
path '/opt/ldap-server/bin/ldap_server.rb'
source 'ldap/ldap_server.rb'
action :create
end
cookbook_file 'core.schema' do
path '/opt/ldap-server/core.schema'
source 'ldap/core.schema'
action :create
end
systemd_unit 'ldap-server.service' do
content(Unit: {
Description: 'Fake Ldap Server',
After: 'network-online.target'
},
Service: {
Type: 'simple',
Restart: 'always',
ExecStart: '/usr/bin/env ruby /opt/ldap-server/bin/ldap_server.rb',
WorkingDirectory: '/opt/ldap-server',
TimeoutSec: 20
},
Install: {
WantedBy: 'multi-user.target'
})
action %i(create enable start)
end
service 'ldap-server.service' do
supports start: true, restart: true, stop: true, status: true, reload: false
end
ruby_block 'create one test entry for ldap server' do
block do
require 'net/ldap'
# ldap client instance
con = Net::LDAP.new(host: 'localhost',
port: 636,
base: 'ou=ldap,o=example.com',
encryption: :simple_tls)
# username and password, empty for simplicity
con.auth '', ''
# authenticate with the server
con.bind
dn = "uid=123456,c=us,ou=ldap,o=example.com"
# attributes to be added
attr = {
uid: "123456",
cn: 'Roger Whatever',
objectclass: 'User',
sn: 'Whatever',
mail: 'Whatever@us.example.com',
c: 'us'
}
# execute the addition
con.add(dn: dn, attributes: attr)
end
end