adap 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +28 -0
- data/README.md +122 -0
- data/Rakefile +10 -0
- data/adap.gemspec +29 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +21 -0
- data/dockerfile/Dockerfile +13 -0
- data/dockerfile/README.md +14 -0
- data/dockerfile/entrypoint.sh +0 -0
- data/lib/adap.rb +7 -0
- data/lib/adap/adap.rb +471 -0
- data/lib/adap/version.rb +3 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ee6e3514fa3c73373536ae3998baa47a70c1a2e528274d7a0b78f49a6a24188d
|
4
|
+
data.tar.gz: 226aed513502e093d6b53bbe3e959ce56b123ac5292643e0497a45d50556694d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 55950748cc4a1c3fb4fd2c6cea48a432760808b26e42773937beb6786771842c433f2e9f62b5923bc529acc35a98eb1d17c8df5a5ab7ef92a330f9045114f404
|
7
|
+
data.tar.gz: d3254d935e294f91dd803ba4128cdec7e29d0e9fe34cee6dfaaf9895ec819a7b2299d7def2ac23e102bf4fa4850aedefd636e9e63a87fba9d4c9b0c008133b1e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
adap (0.0.7)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
minitest (5.14.0)
|
10
|
+
mocha (1.11.2)
|
11
|
+
net-ldap (0.16.2)
|
12
|
+
rake (10.5.0)
|
13
|
+
unix-crypt (1.3.0)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
ruby
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
adap!
|
20
|
+
bundler (~> 2.0)
|
21
|
+
minitest (~> 5.0)
|
22
|
+
mocha (~> 1.10)
|
23
|
+
net-ldap (~> 0.16.2)
|
24
|
+
rake (~> 10.0)
|
25
|
+
unix-crypt (~> 1.3)
|
26
|
+
|
27
|
+
BUNDLED WITH
|
28
|
+
2.1.4
|
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# Adap
|
2
|
+
Adap is a program that synchronize user data on Samba Active Directory (AD) to LDAP.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
|
6
|
+
Add this line to your application's Gemfile:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
gem 'adap'
|
10
|
+
```
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install adap
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
To build this modules, run the command like below.
|
23
|
+
|
24
|
+
```
|
25
|
+
gem build adap.gemspec
|
26
|
+
```
|
27
|
+
|
28
|
+
Then include this module and use it like below.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require "adap"
|
32
|
+
|
33
|
+
adap = Adap.new({
|
34
|
+
:ad_host => "localhost", # Host name or IP of your Active Directory(AD)
|
35
|
+
:ad_binddn => "CN=Administrator,CN=Users,DC=mysite,DC=example,DC=com", # Bind dn of your AD
|
36
|
+
:ad_basedn => "CN=Users,DC=mysite,DC=example,DC=com", # Base dn of your AD
|
37
|
+
:ad_password => "ad_secret", # Password of your AD's bind dn
|
38
|
+
:ldap_host => "ldap_server", # Host name or IP of your LDAP
|
39
|
+
:ldap_binddn => "uid=Administrator,ou=Users,dc=mysite,dc=example,dc=com", # Bind dn of your LDAP
|
40
|
+
:ldap_basedn => "dc=mysite,dc=example,dc=com", # Base dn of your LDAP
|
41
|
+
:ldap_password => "ldap_secret" # Password of your LDAP's bind dn
|
42
|
+
})
|
43
|
+
|
44
|
+
# This operation will synchronize a user taro-suzuki to LDAP from AD
|
45
|
+
adap.sync_user("taro-suzuki")
|
46
|
+
```
|
47
|
+
|
48
|
+
## Requirements and limitations
|
49
|
+
|
50
|
+
This program has some requirements and limitations like below.
|
51
|
+
|
52
|
+
### Not all attributes are synchronized
|
53
|
+
|
54
|
+
Data synchronized to LDAP from AD are limited such as dn, cn uid and uidNumber etc.
|
55
|
+
These attributes are enough to authenticate users to login to Unix-like systems that used an LDAP for authenticating users.
|
56
|
+
|
57
|
+
### AD must be set not to require strong auth
|
58
|
+
|
59
|
+
AD must allow setting `ldap server require strong auth = no` for getting user data.
|
60
|
+
|
61
|
+
* smb.conf of your AD
|
62
|
+
```
|
63
|
+
ldap server require strong auth = no
|
64
|
+
```
|
65
|
+
|
66
|
+
This program will fail to get user data from AD if you did not allow this setting.
|
67
|
+
|
68
|
+
### AD must allow CryptSHA256 or CryptSHA512 to store password and they have to be same as a storing method in LDAP
|
69
|
+
|
70
|
+
AD must allow storing password as CryptSHA256 or CryptSHA512 by setting smb.conf like below.
|
71
|
+
|
72
|
+
* your AD's smb.conf
|
73
|
+
```
|
74
|
+
password hash userPassword schemes = CryptSHA256 CryptSHA512
|
75
|
+
```
|
76
|
+
|
77
|
+
And LDAP have to be configured to store password as sha256(CryptSHA256) or sha512(CryptSHA512).
|
78
|
+
|
79
|
+
For example, you use OpneLDAP, you have to set configuration like below when you store password as sha256.
|
80
|
+
|
81
|
+
```
|
82
|
+
$ ldapmodify -Y EXTERNAL -H ldapi:/// << 'EOF'
|
83
|
+
dn: cn=config
|
84
|
+
add: olcPasswordHash
|
85
|
+
olcPasswordHash: {CRYPT}
|
86
|
+
-
|
87
|
+
add: olcPasswordCryptSaltFormat
|
88
|
+
olcPasswordCryptSaltFormat: $5$%.16s
|
89
|
+
EOF
|
90
|
+
```
|
91
|
+
|
92
|
+
This instruction allows us to save password as sha256 with a salt that length is 16 characters.
|
93
|
+
Or you can store user's password as sha512 with a salt that length is 16 characters like below.
|
94
|
+
|
95
|
+
```
|
96
|
+
$ ldapmodify -Y EXTERNAL -H ldapi:/// << 'EOF'
|
97
|
+
dn: cn=config
|
98
|
+
add: olcPasswordHash
|
99
|
+
olcPasswordHash: {CRYPT}
|
100
|
+
-
|
101
|
+
add: olcPasswordCryptSaltFormat
|
102
|
+
olcPasswordCryptSaltFormat: $6$%.16s
|
103
|
+
EOF
|
104
|
+
```
|
105
|
+
|
106
|
+
### This program must be located in AD server
|
107
|
+
|
108
|
+
This program must be located in AD server because samba-tool on AD only support getting hashed password only from `ldapi://` or `tdb://`.
|
109
|
+
|
110
|
+
### This program only supports syncing user data. Syncing group data does not support yet
|
111
|
+
|
112
|
+
Syncing group data might be supported and implemented if some people demand it.
|
113
|
+
|
114
|
+
## Development
|
115
|
+
|
116
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
117
|
+
|
118
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
119
|
+
|
120
|
+
## Contributing
|
121
|
+
|
122
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/TsutomuNakamura/adap.
|
data/Rakefile
ADDED
data/adap.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "adap/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "adap"
|
7
|
+
spec.version = ModAdap::VERSION
|
8
|
+
spec.authors = ["Tsutomu Nakamura"]
|
9
|
+
spec.email = ["tsuna.0x00@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = %q{LDAP migration tool from AD to NT schema}
|
12
|
+
spec.description = %q{This tool migrate AD LDAP entries to NT LDAP entries.}
|
13
|
+
spec.homepage = "https://github.com/TsutomuNakamura/adap"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
end
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
29
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "adap"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
version: '3'
|
2
|
+
|
3
|
+
services:
|
4
|
+
ruby:
|
5
|
+
container_name: ad-ruby
|
6
|
+
image: tsutomu/adap:docker_bundle_v2.1.2
|
7
|
+
hostname: ruby
|
8
|
+
depends_on:
|
9
|
+
- nt
|
10
|
+
entrypoint:
|
11
|
+
- /opt/entrypoint.sh
|
12
|
+
volumes:
|
13
|
+
- .:/opt/app
|
14
|
+
privileged: true
|
15
|
+
|
16
|
+
nt:
|
17
|
+
container_name: nt
|
18
|
+
hostname: nt
|
19
|
+
image: tsutomu/docker-samba-nt
|
20
|
+
privileged: true
|
21
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
FROM tsutomu/docker-samba-ad
|
2
|
+
LABEL maintainer "Tsutomu Nakamura<tsuna.0x00@gmail.com>"
|
3
|
+
|
4
|
+
RUN DEBIAN_FRONTEND=noninteractive apt-get update \
|
5
|
+
&& DEBIAN_FRONTEND=noninteractive apt-get -y install \
|
6
|
+
curl ruby-full ruby-bundler \
|
7
|
+
&& gem update --system \
|
8
|
+
&& rm -rf /usr/share/rubygems-integration/all/specifications/rake-* \
|
9
|
+
&& apt-get clean
|
10
|
+
|
11
|
+
ENV LC_ALL=C.UTF-8
|
12
|
+
ENV LANG=C.UTF-8
|
13
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# For testing
|
2
|
+
after the container up, you can test connectivity by executing command like below.
|
3
|
+
|
4
|
+
```
|
5
|
+
ldapsearch -x -LLL -o 'ldif-wrap=no' -h ad -w "p@ssword0" \
|
6
|
+
-D "CN=Administrator,CN=Users,DC=mysite,DC=example,DC=com" \
|
7
|
+
-b "CN=Users,DC=mysite,DC=example,DC=com" \
|
8
|
+
'(objectCategory=CN=Person,CN=Schema,CN=Configuration,DC=mysite,DC=example,DC=com)'
|
9
|
+
```
|
10
|
+
|
11
|
+
```
|
12
|
+
samba-tool user getpassword taro-suzuki --attribute virtualCryptSHA512
|
13
|
+
```
|
14
|
+
|
File without changes
|
data/lib/adap.rb
ADDED
data/lib/adap/adap.rb
ADDED
@@ -0,0 +1,471 @@
|
|
1
|
+
require 'net-ldap'
|
2
|
+
|
3
|
+
class Adap
|
4
|
+
|
5
|
+
# :unixhomedirectory and :homedirectory are the attributes that has same meaning between AD and LDAP.
|
6
|
+
USER_REQUIRED_ATTRIBUTES = [:cn, :sn, :uid, :uidnumber, :gidnumber, :displayname, :loginshell, :gecos, :givenname, :unixhomedirectory, :homedirectory]
|
7
|
+
#USER_REQUIRED_ATTRIBUTES = ['cn', 'sn', 'uid', 'uidNumber', 'gidNumber', 'homeDirectory', 'loginShell', 'gecos', 'givenName']
|
8
|
+
GROUP_OF_USER_REQUIRED_ATTRIBUTES = [:objectclass, :gidnumber, :cn, :description, :memberuid]
|
9
|
+
|
10
|
+
#
|
11
|
+
# params {
|
12
|
+
# :ad_host required IP or hostname of AD.
|
13
|
+
# :ad_port optional (default:389) Port of AD host.
|
14
|
+
# :ad_binddn required Binddn of AD.
|
15
|
+
# :ad_basedn required Basedn of AD users.
|
16
|
+
# :ad_password optional (default:(empty)) Password of AD with :ad_binddn.
|
17
|
+
# :ldap_host required IP or hostname of NT.
|
18
|
+
# :ldap_port optional (default:389) Port of NT host.
|
19
|
+
# :ldap_binddn required Binddn of NT.
|
20
|
+
# :ldap_basedn required Basedn of NT users.
|
21
|
+
# :ldap_password optional (default:(same as :ad_password)) Password of NT with :ldap_binddn
|
22
|
+
# :password_hash_algorithm (default:virtualCryptSHA512) Password hash algorithm. It must be same between AD and LDAP, and only support virtualCryptSHA512 or virtualCryptSHA256 now.
|
23
|
+
# }
|
24
|
+
#
|
25
|
+
def initialize(params)
|
26
|
+
raise "Initialize Adap was failed. params must not be nil" if params == nil
|
27
|
+
#raise 'Adap requires keys of parameter "ad_host" "ad_binddn" "ad_basedn"' \
|
28
|
+
[:ad_host, :ad_binddn, :ad_basedn, :ldap_host, :ldap_binddn, :ldap_basedn].each { |k|
|
29
|
+
raise 'Adap requires keys in params ":ad_host", ":ad_binddn", ":ad_basedn", ":ldap_host", ":ldap_binddn", ":ldap_basedn"' if !params.key?(k)
|
30
|
+
}
|
31
|
+
|
32
|
+
@ad_host = params[:ad_host]
|
33
|
+
@ad_port = (params[:ad_port] ? params[:ad_port] : 389)
|
34
|
+
@ad_binddn = params[:ad_binddn]
|
35
|
+
@ad_basedn = params[:ad_basedn]
|
36
|
+
@ad_auth = (params.has_key?(:ad_password) ? { :method => :simple, :username => @ad_binddn, :password => params[:ad_password] } : nil)
|
37
|
+
@ldap_host = params[:ldap_host]
|
38
|
+
@ldap_port = (params[:ldap_port] ? params[:ldap_port] : 389)
|
39
|
+
@ldap_binddn = params[:ldap_binddn]
|
40
|
+
@ldap_basedn = params[:ldap_basedn]
|
41
|
+
@ldap_user_basedn = params[:ldap_user_basedn]
|
42
|
+
@ldap_auth = (params.has_key?(:ldap_password) ? { :method => :simple, :username => @ldap_binddn, :password => params[:ldap_password] } : nil )
|
43
|
+
@password_hash_algorithm = (params[:password_hash_algorithm] ? params[:password_hash_algorithm] : 'virtualCryptSHA512')
|
44
|
+
|
45
|
+
@ad_client = Adap::get_ad_client_instance(@ad_host, @ad_port, @ad_auth)
|
46
|
+
@ldap_client = Adap::get_ldap_client_instance(@ldap_host, @ldap_port, @ldap_auth)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.get_ad_client_instance(ad_host, ad_port, ad_auth)
|
50
|
+
Net::LDAP.new(:host => ad_host, :port => ad_port, :auth => ad_auth)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.get_ldap_client_instance(ldap_host, ldap_port, ldap_auth)
|
54
|
+
Net::LDAP.new(:host => ldap_host, :port => ldap_port, :auth => ldap_auth)
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_ad_dn(username)
|
58
|
+
"CN=#{username},CN=Users,#{@ad_basedn}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_ldap_dn(username)
|
62
|
+
"uid=#{username},ou=Users,#{@ldap_basedn}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_ldap_attributes(entry)
|
66
|
+
attributes = {
|
67
|
+
:objectclass => ["top", "person", "organizationalPerson", "inetOrgPerson", "posixAccount", "shadowAccount"]
|
68
|
+
}
|
69
|
+
|
70
|
+
entry.each do |attribute, values|
|
71
|
+
# Change string to lower case symbols to compare each attributes correctly
|
72
|
+
attribute = attribute.downcase.to_sym
|
73
|
+
|
74
|
+
if USER_REQUIRED_ATTRIBUTES.include?(attribute) then
|
75
|
+
if attribute == :unixhomedirectory then
|
76
|
+
attributes[:homedirectory] = values
|
77
|
+
else
|
78
|
+
attributes[attribute] = values
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attributes
|
84
|
+
end
|
85
|
+
|
86
|
+
def get_password(username)
|
87
|
+
password = get_raw_password(username, @password_hash_algorithm)
|
88
|
+
|
89
|
+
if password == nil || password.empty?
|
90
|
+
raise "Failed to get password of #{username} from AD. Did you enabled AD password option virtualCryptSHA512 and/or virtualCryptSHA256?"
|
91
|
+
end
|
92
|
+
password = password.chomp
|
93
|
+
|
94
|
+
password
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_raw_password(username, algo)
|
98
|
+
`samba-tool user getpassword #{username} --attribute #{algo} 2> /dev/null | grep -E '^virtualCrypt' -A 1 | tr -d ' \n' | cut -d ':' -f 2`
|
99
|
+
end
|
100
|
+
|
101
|
+
def sync_user(uid)
|
102
|
+
ad_entry = nil
|
103
|
+
ldap_entry = nil
|
104
|
+
ad_dn = get_ad_dn(uid)
|
105
|
+
ldap_dn = get_ldap_dn(uid)
|
106
|
+
|
107
|
+
# dn: CN=user-name,CN=Users,DC=mysite,DC=example,DC=com
|
108
|
+
@ad_client.search(:base => ad_dn) do |entry|
|
109
|
+
ad_entry = entry
|
110
|
+
end
|
111
|
+
ret_code = @ad_client.get_operation_result.code
|
112
|
+
|
113
|
+
return {
|
114
|
+
:code => ret_code,
|
115
|
+
:operations => nil,
|
116
|
+
:message => "Failed to get a user #{ad_dn} from AD - " + @ad_client.get_operation_result.error_message
|
117
|
+
} if ret_code != 0 && ret_code != 32
|
118
|
+
|
119
|
+
@ldap_client.search(:base => ldap_dn) do |entry|
|
120
|
+
ldap_entry = entry
|
121
|
+
end
|
122
|
+
ret_code = @ldap_client.get_operation_result.code
|
123
|
+
|
124
|
+
return {
|
125
|
+
:code => ret_code,
|
126
|
+
:operations => nil,
|
127
|
+
:message => "Failed to get a user #{ldap_dn} from LDAP - " + @ldap_client.get_operation_result.error_message
|
128
|
+
} if ret_code != 0 && ret_code != 32
|
129
|
+
|
130
|
+
ret = nil
|
131
|
+
if !ad_entry.nil? and ldap_entry.nil? then
|
132
|
+
ret = add_user(ldap_dn, ad_entry, get_password(uid))
|
133
|
+
elsif ad_entry.nil? and !ldap_entry.nil? then
|
134
|
+
ret = delete_user(ldap_dn)
|
135
|
+
elsif !ad_entry.nil? and !ldap_entry.nil? then
|
136
|
+
ret = modify_user(ldap_dn, ad_entry, ldap_entry, get_password(uid))
|
137
|
+
else
|
138
|
+
# ad_entry.nil? and ldap_entry.nil? then
|
139
|
+
return {:code => 0, :operations => nil, :message => "There are not any data of #{uid} to sync."}
|
140
|
+
end
|
141
|
+
|
142
|
+
return ret if ret[:code] != 0
|
143
|
+
|
144
|
+
# Sync groups belonging the user next if syncing data of the user has succeeded.
|
145
|
+
ret_sync_group = sync_group_of_user(uid, get_primary_gidnumber(ad_entry))
|
146
|
+
|
147
|
+
return {
|
148
|
+
:code => ret_sync_group[:code],
|
149
|
+
:operations => ret[:operations].concat(ret_sync_group[:operations]),
|
150
|
+
:message => (
|
151
|
+
ret_sync_group[:code] == 0 ?
|
152
|
+
nil : "Syncing a user #{uid} has succeeded but syncing its groups have failed. Message: " + ret_sync_group[:message]
|
153
|
+
)
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_user(ldap_user_dn, ad_entry, password)
|
158
|
+
attributes = create_ldap_attributes(ad_entry)
|
159
|
+
|
160
|
+
@ldap_client.add(
|
161
|
+
:dn => ldap_user_dn,
|
162
|
+
:attributes => attributes
|
163
|
+
)
|
164
|
+
ret_code = @ldap_client.get_operation_result.code
|
165
|
+
|
166
|
+
return {
|
167
|
+
:code => ret_code,
|
168
|
+
:operations => [:add_user],
|
169
|
+
:message => "Failed to add a user #{ldap_user_dn} in add_user() - " + @ldap_client.get_operation_result.error_message
|
170
|
+
} if ret_code != 0
|
171
|
+
|
172
|
+
@ldap_client.modify(
|
173
|
+
:dn => ldap_user_dn,
|
174
|
+
:operations => [
|
175
|
+
[:add, :userPassword, password]
|
176
|
+
]
|
177
|
+
)
|
178
|
+
ret_code = @ldap_client.get_operation_result.code
|
179
|
+
|
180
|
+
return {
|
181
|
+
:code => ret_code,
|
182
|
+
:operations => [:add_user],
|
183
|
+
:message => "Failed to modify a user #{ldap_user_dn} in add_user() - " + @ldap_client.get_operation_result.error_message
|
184
|
+
} if ret_code != 0
|
185
|
+
|
186
|
+
return {:code => ret_code, :operations => [:add_user], :message => nil}
|
187
|
+
end
|
188
|
+
|
189
|
+
def modify_user(ldap_user_dn, ad_entry, ldap_entry, password)
|
190
|
+
# An attribute objectClass will not be sync because it assumed already added by add_user() function or another method in LDAP.
|
191
|
+
operations = create_modify_operations(ad_entry, ldap_entry, password)
|
192
|
+
|
193
|
+
@ldap_client.modify(
|
194
|
+
:dn => ldap_user_dn,
|
195
|
+
:operations => operations
|
196
|
+
)
|
197
|
+
ret_code = @ldap_client.get_operation_result.code
|
198
|
+
|
199
|
+
return {
|
200
|
+
:code => ret_code,
|
201
|
+
:operations => [:modify_user],
|
202
|
+
:message => "Failed to modify a user #{ldap_user_dn} in modify_user() - " + @ldap_client.get_operation_result.error_message
|
203
|
+
} if ret_code != 0
|
204
|
+
|
205
|
+
return {:code => ret_code, :operations => [:modify_user], :message => nil}
|
206
|
+
end
|
207
|
+
|
208
|
+
def create_modify_operations(ad_entry, ldap_entry, password)
|
209
|
+
operations = []
|
210
|
+
|
211
|
+
ad_entry.each do |key, value|
|
212
|
+
ad_key_sym = key.downcase.to_sym
|
213
|
+
ldap_key = (key != :unixhomedirectory ? key : :homedirectory)
|
214
|
+
ldap_key_sym = ldap_key.downcase.to_sym
|
215
|
+
|
216
|
+
if USER_REQUIRED_ATTRIBUTES.include?(ad_key_sym)
|
217
|
+
next if value == ldap_entry[ldap_key]
|
218
|
+
operations.push((ldap_entry[ldap_key] != nil ? [:replace, ldap_key_sym, value] : [:add, ldap_key_sym, value]))
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
ldap_entry.each do |key, value|
|
223
|
+
ldap_key_sym = key.downcase.to_sym
|
224
|
+
ad_key = (key != :homedirectory ? key : :unixhomedirectory)
|
225
|
+
|
226
|
+
if USER_REQUIRED_ATTRIBUTES.include?(ldap_key_sym)
|
227
|
+
operations.push([:delete, ldap_key_sym, nil]) if ad_entry[ad_key] == nil
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# AD does not have password as simple ldap attribute.
|
232
|
+
# So password will always be updated for this reason.
|
233
|
+
operations.push([:replace, :userpassword, password])
|
234
|
+
|
235
|
+
operations
|
236
|
+
end
|
237
|
+
|
238
|
+
def delete_user(ldap_user_dn)
|
239
|
+
@ldap_client.delete(:dn => ldap_user_dn)
|
240
|
+
ret_code = @ldap_client.get_operation_result.code
|
241
|
+
|
242
|
+
return {
|
243
|
+
:code => ret_code,
|
244
|
+
:operations => [:delete_user],
|
245
|
+
:message => "Failed to delete a user #{ldap_user_dn} in delete_user() - " + @ldap_client.get_operation_result.error_message
|
246
|
+
} if ret_code != 0
|
247
|
+
|
248
|
+
return {:code => ret_code, :operations => [:delete_user], :message => nil}
|
249
|
+
end
|
250
|
+
|
251
|
+
def sync_group_of_user(uid, primary_gid_number)
|
252
|
+
ad_group_map = {}
|
253
|
+
ldap_group_map = {}
|
254
|
+
|
255
|
+
# Creating AD ldapsearch filter
|
256
|
+
|
257
|
+
ad_filter = if primary_gid_number == nil then
|
258
|
+
Net::LDAP::Filter.construct(
|
259
|
+
"(&(objectCategory=CN=Group,CN=Schema,CN=Configuration,#{@ad_basedn})(member=CN=#{uid},CN=Users,#{@ad_basedn}))")
|
260
|
+
else
|
261
|
+
Net::LDAP::Filter.construct(
|
262
|
+
"(&(objectCategory=CN=Group,CN=Schema,CN=Configuration,#{@ad_basedn})(|(member=CN=#{uid},CN=Users,#{@ad_basedn})(gidNumber=#{primary_gid_number})))")
|
263
|
+
end
|
264
|
+
|
265
|
+
# Get groups from AD
|
266
|
+
# entry = {
|
267
|
+
# :gidnumber => xxx,
|
268
|
+
# }
|
269
|
+
#
|
270
|
+
@ad_client.search(:base => @ad_basedn, :filter => ad_filter) do |entry|
|
271
|
+
ad_group_map[entry[:name].first] = {:gidnumber => entry[:gidnumber]}
|
272
|
+
#ad_group_map[entry[:name]] = nil
|
273
|
+
end
|
274
|
+
ret_code = @ad_client.get_operation_result.code
|
275
|
+
|
276
|
+
return {
|
277
|
+
:code => ret_code,
|
278
|
+
:operations => [:search_groups_from_ad],
|
279
|
+
:message => "Failed to get groups of a user #{uid} from AD to sync them. " + @ad_client.get_operation_result.error_message
|
280
|
+
} if ret_code != 0 && ret_code != 32
|
281
|
+
|
282
|
+
# Create LDAP ldapsearch filter
|
283
|
+
ldap_filter = Net::LDAP::Filter.construct("(memberUid=#{uid})")
|
284
|
+
|
285
|
+
# Get groups from LDAP
|
286
|
+
@ldap_client.search(:base => "ou=Groups," + @ldap_basedn, :filter => ldap_filter) do |entry|
|
287
|
+
# gidnumber is not necessary for LDAP entry
|
288
|
+
ldap_group_map[entry[:cn].first] = nil
|
289
|
+
end
|
290
|
+
ret_code = @ldap_client.get_operation_result.code
|
291
|
+
|
292
|
+
return {
|
293
|
+
:code => ret_code,
|
294
|
+
:operations => [:search_groups_from_ldap],
|
295
|
+
:message => "Failed to get groups of a user #{uid} from LDAP to sync them. " + @ldap_client.get_operation_result.error_message
|
296
|
+
} if ret_code != 0
|
297
|
+
|
298
|
+
# Comparing name of AD's entry and cn of LDAP's entry
|
299
|
+
operation_pool = create_sync_group_of_user_operation(ad_group_map, ldap_group_map, uid)
|
300
|
+
ret = do_sync_group_of_user_operation(operation_pool)
|
301
|
+
|
302
|
+
return {
|
303
|
+
:code => ret[:code],
|
304
|
+
:operations => [:modify_group_of_user],
|
305
|
+
:message => (ret[:code] == 0 ? nil: ret[:message])
|
306
|
+
}
|
307
|
+
end
|
308
|
+
|
309
|
+
# {
|
310
|
+
# "cn=foo,ou=Groups,dc=mysite,dc=example,dc=com": {
|
311
|
+
# :cn => "foo",
|
312
|
+
# :gidnumber => xxx,
|
313
|
+
# :operations => [[:add, :memberuid, uid]]
|
314
|
+
# }
|
315
|
+
# "cn=bar,ou=Groups,dc=mysite,dc=example,dc=com": {
|
316
|
+
# :cn => "bar",
|
317
|
+
# :gidnumber => yyy,
|
318
|
+
# :operations => [[:delete, :memberuid, uid]]
|
319
|
+
# }
|
320
|
+
# }
|
321
|
+
def create_sync_group_of_user_operation(ad_group_map, ldap_group_map, uid)
|
322
|
+
operation_pool = {}
|
323
|
+
|
324
|
+
ad_group_map.each_key do |key|
|
325
|
+
dn = "cn=#{key},ou=Groups,#{@ldap_basedn}"
|
326
|
+
# Convert AD entries to LDAP entries to create operation to update LDAP data.
|
327
|
+
operation_pool[dn] = {
|
328
|
+
:cn => key,
|
329
|
+
:gidnumber => ad_group_map[key][:gidnumber],
|
330
|
+
:operations => [[:add, :memberuid, uid]]
|
331
|
+
} if !ldap_group_map.has_key?(key)
|
332
|
+
end
|
333
|
+
|
334
|
+
ldap_group_map.each_key do |key|
|
335
|
+
operation_pool["cn=#{key},ou=Groups,#{@ldap_basedn}"] = {
|
336
|
+
# :cn and :gidnumber are not necessary
|
337
|
+
:operations => [[:delete, :memberuid, uid]]
|
338
|
+
} if !ad_group_map.has_key?(key)
|
339
|
+
end
|
340
|
+
|
341
|
+
operation_pool
|
342
|
+
end
|
343
|
+
|
344
|
+
def do_sync_group_of_user_operation(operation_pool)
|
345
|
+
return {
|
346
|
+
:code => 0,
|
347
|
+
:operations => nil,
|
348
|
+
:message => "There are not any groups of user to sync"
|
349
|
+
} if operation_pool.length == 0
|
350
|
+
|
351
|
+
# ex)
|
352
|
+
# entry_key = "cn=bar,ou=Groups,dc=mysite,dc=example,dc=com"
|
353
|
+
operation_pool.each_key do |entry_key|
|
354
|
+
|
355
|
+
# ex)
|
356
|
+
# entry = {
|
357
|
+
# :cn => "bar",
|
358
|
+
# :gidnumber => 1000,
|
359
|
+
# :operations => [[:add, :memberuid, uid]] # or [[:delete, :memberuid, uid]]
|
360
|
+
# }
|
361
|
+
entry = operation_pool[entry_key]
|
362
|
+
|
363
|
+
if entry[:operations].first.first == :add then
|
364
|
+
ret = add_group_if_not_existed(entry_key, entry)
|
365
|
+
return ret if ret[:code] != 0
|
366
|
+
end
|
367
|
+
# The operation will be like...
|
368
|
+
# [[:add, :memberuid, "username"]] or [[:delete, :memberuid, "username"]]
|
369
|
+
|
370
|
+
@ldap_client.modify({
|
371
|
+
:dn => entry_key,
|
372
|
+
:operations => entry[:operations]
|
373
|
+
})
|
374
|
+
ret_code = @ldap_client.get_operation_result.code
|
375
|
+
|
376
|
+
return {
|
377
|
+
:code => ret_code,
|
378
|
+
:operations => [:modify_group_of_user],
|
379
|
+
:message => "Failed to modify group \"#{entry_key}\" of user #{entry[:cn]}. " + @ldap_client.get_operation_result.error_message
|
380
|
+
} if ret_code != 0
|
381
|
+
|
382
|
+
if entry[:operations].first.first == :delete then
|
383
|
+
ret = delete_group_if_existed_as_empty(entry_key)
|
384
|
+
return ret if ret[:code] != 0
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
return {:code => 0, :operations => [:modify_group_of_user], :message => nil}
|
389
|
+
end
|
390
|
+
|
391
|
+
def add_group_if_not_existed(group_dn, entry)
|
392
|
+
@ldap_client.search(:base => group_dn)
|
393
|
+
ret_code = @ldap_client.get_operation_result.code
|
394
|
+
|
395
|
+
# The group already existed
|
396
|
+
return {:code => 0, :operations => nil, :message => nil} if ret_code == 0
|
397
|
+
|
398
|
+
# Failed to query ldapsearch for some reason
|
399
|
+
return {
|
400
|
+
:code => ret_code,
|
401
|
+
:operations => nil,
|
402
|
+
:message => "Failed to search LDAP in add_group_if_not_existed(). " + @ldap_client.get_operation_result.error_message
|
403
|
+
} if ret_code != 32
|
404
|
+
|
405
|
+
attributes = {:objectclass => ["top", "posixGroup"]}
|
406
|
+
attributes[:gidnumber] = entry[:gidnumber] if entry[:gidnumber] != nil
|
407
|
+
attributes[:cn] = entry[:cn] if entry[:cn] != nil
|
408
|
+
|
409
|
+
@ldap_client.add(
|
410
|
+
:dn => group_dn,
|
411
|
+
:attributes => attributes
|
412
|
+
)
|
413
|
+
ret_code = @ldap_client.get_operation_result.code
|
414
|
+
|
415
|
+
return {
|
416
|
+
:code => ret_code,
|
417
|
+
:operations => [:add_group],
|
418
|
+
:message => (ret_code == 0 ? nil : "Failed to add a group in add_group_if_not_existed(). " + @ldap_client.get_operation_result.error_message)
|
419
|
+
}
|
420
|
+
end
|
421
|
+
|
422
|
+
def delete_group_if_existed_as_empty(group_dn)
|
423
|
+
is_no_memberuid = false
|
424
|
+
# Check whether the group has memberuid
|
425
|
+
@ldap_client.search(:base => group_dn, :filter => "(!(memberUid=*))") do |e|
|
426
|
+
is_no_memberuid = true
|
427
|
+
end
|
428
|
+
|
429
|
+
ret_code = @ldap_client.get_operation_result.code
|
430
|
+
return {:code => 0, :operations => nil, :message => nil} \
|
431
|
+
if (ret_code == 0 && is_no_memberuid == false) || ret_code == 32
|
432
|
+
|
433
|
+
return {
|
434
|
+
:code => ret_code,
|
435
|
+
:operations => nil,
|
436
|
+
:message => "Failed to search group in delete_group_if_existed_as_empty(). " + @ldap_client.get_operation_result.error_message
|
437
|
+
} if ret_code != 0
|
438
|
+
|
439
|
+
@ldap_client.delete(:dn => group_dn)
|
440
|
+
ret_code = @ldap_client.get_operation_result.code
|
441
|
+
|
442
|
+
return {
|
443
|
+
:code => ret_code,
|
444
|
+
:operations => [:delete_group],
|
445
|
+
:message => (ret_code == 0 ? nil: "Failed to delete a group in delete_group_if_existed_as_empty(). " + @ldap_client.get_operation_result.error_message)
|
446
|
+
}
|
447
|
+
end
|
448
|
+
|
449
|
+
def get_primary_gidnumber(entry)
|
450
|
+
return nil if entry == nil
|
451
|
+
|
452
|
+
if entry[:primarygroupid] == nil then
|
453
|
+
ad_result = get_primary_gidnumber_from_ad(entry[:uid].first)
|
454
|
+
return ad_result
|
455
|
+
end
|
456
|
+
|
457
|
+
return entry[:primarygroupid].first
|
458
|
+
end
|
459
|
+
|
460
|
+
def get_primary_gidnumber_from_ad(uid)
|
461
|
+
return nil if uid ==nil
|
462
|
+
|
463
|
+
@ad_client.search(:base => "CN=#{uid},CN=Users,#{@ad_basedn}") do |entry|
|
464
|
+
primary_gid = entry[:gidnumber].first
|
465
|
+
end
|
466
|
+
|
467
|
+
return primary_gid
|
468
|
+
end
|
469
|
+
|
470
|
+
end
|
471
|
+
|
data/lib/adap/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.12
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tsutomu Nakamura
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
description: This tool migrate AD LDAP entries to NT LDAP entries.
|
56
|
+
email:
|
57
|
+
- tsuna.0x00@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
64
|
+
- Gemfile
|
65
|
+
- Gemfile.lock
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- adap.gemspec
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- docker-compose.yml
|
72
|
+
- dockerfile/Dockerfile
|
73
|
+
- dockerfile/README.md
|
74
|
+
- dockerfile/entrypoint.sh
|
75
|
+
- lib/adap.rb
|
76
|
+
- lib/adap/adap.rb
|
77
|
+
- lib/adap/version.rb
|
78
|
+
homepage: https://github.com/TsutomuNakamura/adap
|
79
|
+
licenses: []
|
80
|
+
metadata:
|
81
|
+
homepage_uri: https://github.com/TsutomuNakamura/adap
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.1.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: LDAP migration tool from AD to NT schema
|
101
|
+
test_files: []
|